From f7f4c17668867c0d387ad3af255c156d0a0172f7 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 23 Sep 2024 16:20:56 -0400 Subject: [PATCH 001/113] DOCSP-42732: qs --- mongoid | 1 + snooty.toml | 2 ++ source/index.txt | 18 ++++++------- source/quick-start.txt | 57 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 9 deletions(-) create mode 160000 mongoid create mode 100644 source/quick-start.txt diff --git a/mongoid b/mongoid new file mode 160000 index 00000000..f493754d --- /dev/null +++ b/mongoid @@ -0,0 +1 @@ +Subproject commit f493754d1cd70f89f2d77ba977ca280c87672e79 diff --git a/snooty.toml b/snooty.toml index 9eb93a00..d44f174f 100644 --- a/snooty.toml +++ b/snooty.toml @@ -6,3 +6,5 @@ intersphinx = ["https://www.mongodb.com/docs/manual/objects.inv"] [constants] rails-6-version = 6.0 rails-7-version = 7.1 +odm = "Mongoid" +ruby-driver = "Ruby driver" diff --git a/source/index.txt b/source/index.txt index 0f320dd5..91a3a1a2 100644 --- a/source/index.txt +++ b/source/index.txt @@ -1,18 +1,18 @@ -.. _mongoid: +.. _mongoid-odm: -******* -Mongoid -******* +======= +{+odm+} +======= -.. default-domain:: mongodb - -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. +{+odm+} is the officially supported object-document mapper (ODM) for +MongoDB in Ruby. To work with {+odm+} from the command line using +``rails``-like tooling, the `railsmdb +`_ utility can be used. .. toctree:: :titlesonly: + /quick-start installation-configuration tutorials schema-configuration diff --git a/source/quick-start.txt b/source/quick-start.txt new file mode 100644 index 00000000..ee56035a --- /dev/null +++ b/source/quick-start.txt @@ -0,0 +1,57 @@ +.. _mongoid-quick-start: + +=========== +Quick Start +=========== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: php framework, odm + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +Overview +-------- + +This guide shows you how to use {+odm+} in a new web application, +connect to a MongoDB cluster hosted on MongoDB Atlas, and perform +read and write operations on the data. + +.. tip:: + + If you prefer to connect to MongoDB by using the {+ruby-driver+} without + {+odm+}, see the {+ruby-driver+} :driver:`Quick Start guide + `. + +{+odm+} is an Object-Document Mapper (ODM) framework for MongoDB in +Ruby. + +MongoDB Atlas is a fully managed cloud database service that hosts your +MongoDB deployments. You can create your own free (no credit card +required) MongoDB Atlas deployment by following the steps in this guide. + +Follow the steps in this guide to create a sample {+odm+} web application +that connects to a MongoDB deployment. + +.. TODO .. tip:: +.. +.. You can download the complete web application project by cloning the +.. `mongoid-quickstart <>`__ GitHub repository. + +.. toctree:: + + /quick-start/download-and-install/ + +.. /quick-start/create-a-deployment/ +.. /quick-start/create-a-connection-string/ +.. /quick-start/configure-mongodb/ +.. /quick-start/view-data/ +.. /quick-start/write-data/ +.. /quick-start/next-steps/ From 482cb67b7bdbbec3c17cfc3bfa52ddbe8ab1bb79 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 23 Sep 2024 16:23:15 -0400 Subject: [PATCH 002/113] fix --- .dockerignore | 2 -- mongoid | 1 - 2 files changed, 3 deletions(-) delete mode 100644 .dockerignore delete mode 160000 mongoid diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index c2c70813..00000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -# Cannot dockerignore .git - giza insists on using git on the docs repo -/build diff --git a/mongoid b/mongoid deleted file mode 160000 index f493754d..00000000 --- a/mongoid +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f493754d1cd70f89f2d77ba977ca280c87672e79 From 891e0f2b843039cf8ed49bc4cec7657aec7af3f5 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 23 Sep 2024 16:28:31 -0400 Subject: [PATCH 003/113] wip --- snooty.toml | 8 +++++++- source/quick-start.txt | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/snooty.toml b/snooty.toml index d44f174f..07458bfa 100644 --- a/snooty.toml +++ b/snooty.toml @@ -1,7 +1,13 @@ name = "mongoid" title = "Mongoid" -intersphinx = ["https://www.mongodb.com/docs/manual/objects.inv"] +intersphinx = [ "https://www.mongodb.com/docs/manual/objects.inv", + "https://www.mongodb.com/docs/atlas/objects.inv" + ] + +toc_landing_pages = [ + "/quick-start" + ] [constants] rails-6-version = 6.0 diff --git a/source/quick-start.txt b/source/quick-start.txt index ee56035a..724a2188 100644 --- a/source/quick-start.txt +++ b/source/quick-start.txt @@ -27,8 +27,8 @@ read and write operations on the data. .. tip:: If you prefer to connect to MongoDB by using the {+ruby-driver+} without - {+odm+}, see the {+ruby-driver+} :driver:`Quick Start guide - `. + {+odm+}, see the {+ruby-driver+} :ruby:`Quick Start guide + `. {+odm+} is an Object-Document Mapper (ODM) framework for MongoDB in Ruby. From 87689f28fd88d07c071d5b7597e091dee3cc1f66 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 24 Sep 2024 10:12:05 -0400 Subject: [PATCH 004/113] remove action --- .github/workflows/check-autobuilder.yml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .github/workflows/check-autobuilder.yml diff --git a/.github/workflows/check-autobuilder.yml b/.github/workflows/check-autobuilder.yml deleted file mode 100644 index 7e15fe91..00000000 --- a/.github/workflows/check-autobuilder.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Check Autobuilder for Errors - -on: - pull_request: - paths: - - "source/**" - -jobs: - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: cbush/snooty-autobuilder-check@main \ No newline at end of file From cfa5ff633210b6dde3732d67a3cc31e4b88ca75b Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 25 Sep 2024 15:39:28 -0400 Subject: [PATCH 005/113] NR suggestion --- source/index.txt | 4 ++-- source/quick-start.txt | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/index.txt b/source/index.txt index 91a3a1a2..282139a0 100644 --- a/source/index.txt +++ b/source/index.txt @@ -6,8 +6,8 @@ {+odm+} is the officially supported object-document mapper (ODM) for MongoDB in Ruby. To work with {+odm+} from the command line using -``rails``-like tooling, the `railsmdb -`_ utility can be used. +``rails``-like tooling, you can use the `railsmdb +`_ utility. .. toctree:: :titlesonly: diff --git a/source/quick-start.txt b/source/quick-start.txt index 724a2188..1ab513c0 100644 --- a/source/quick-start.txt +++ b/source/quick-start.txt @@ -22,7 +22,7 @@ Overview This guide shows you how to use {+odm+} in a new web application, connect to a MongoDB cluster hosted on MongoDB Atlas, and perform -read and write operations on the data. +read and write operations on the data in your cluster. .. tip:: @@ -31,7 +31,8 @@ read and write operations on the data. `. {+odm+} is an Object-Document Mapper (ODM) framework for MongoDB in -Ruby. +Ruby. By using {+odm+}, you can easily interact with your data and +create flexible data models. MongoDB Atlas is a fully managed cloud database service that hosts your MongoDB deployments. You can create your own free (no credit card From dbcc740ca32e45a0c8d9e4f4a3809f37f9c7cebf Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 27 Sep 2024 14:25:52 -0400 Subject: [PATCH 006/113] DOCSP-42732: qs download --- Dockerfile | 27 ----- Makefile | 112 ------------------- conf-sitemap.xml | 26 ----- entrypoint.sh | 9 -- snooty.toml | 5 + source/Makefile | 20 ---- source/README.md | 14 --- source/conf.py | 57 ---------- source/includes/quick-start/troubleshoot.rst | 6 + source/meta/404.txt | 7 -- source/quick-start/download-and-install.txt | 95 ++++++++++++++++ source/reference/compatibility.txt | 18 ++- worker.sh | 1 - 13 files changed, 114 insertions(+), 283 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Makefile delete mode 100644 conf-sitemap.xml delete mode 100755 entrypoint.sh delete mode 100644 source/Makefile delete mode 100644 source/README.md delete mode 100644 source/conf.py create mode 100644 source/includes/quick-start/troubleshoot.rst delete mode 100644 source/meta/404.txt create mode 100644 source/quick-start/download-and-install.txt delete mode 100644 worker.sh diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1fabdde6..00000000 --- a/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Debian 9 ships with Python 3.5 which is not usable with mut -# (https://jira.mongodb.org/browse/DOP-1301, fixed by -# https://github.com/p-mongo/mut/commit/5beefc1c9ac2a9afabb86d918bfe598477b927e1). -# Debian 10 ships with Python 3.7 which is supposed to work but it doesn't -# per https://jira.mongodb.org/browse/DOP-1303. -# Use Debian 9 with the patch. -FROM debian:9 - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get -y install make git python-pip python3-pip \ - ruby yard pkg-config libxml2-dev && \ - python -m pip install giza && \ - python3 -m pip install mut - -# Apply the fix in -# https://github.com/p-mongo/mut/commit/5beefc1c9ac2a9afabb86d918bfe598477b927e1 -RUN git clone https://github.com/p-mongo/mut && \ - cd mut && \ - python3 setup.py install - -WORKDIR /app - -COPY . . - -ENTRYPOINT ["./entrypoint.sh"] diff --git a/Makefile b/Makefile deleted file mode 100644 index a6b19a5b..00000000 --- a/Makefile +++ /dev/null @@ -1,112 +0,0 @@ -# Makefile for Mongoid docs - -GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) -USER=$(shell whoami) -STAGING_URL="https://docs-mongodborg-staging.corp.mongodb.com" -PRODUCTION_URL="https://docs.mongodb.com" - -STAGING_BUCKET=docs-mongodb-org-prd-staging -PRODUCTION_BUCKET=docs-mongodb-org-prd - -SEARCH_INDEX_BUCKET=docs-search-indexes-test - -PROJECT=mongoid -PREFIX=mongoid -TARGET_DIR=source-${GIT_BRANCH} - -SOURCE_FILE_DIR=build/mongoid-${GIT_BRANCH} -DOTCOM_STAGING_URL="https://mongodbcom-cdn.website.staging.corp.mongodb.com" -DOTCOM_STAGING_BUCKET=docs-mongodb-org-dotcomstg -DOTCOM_PRODUCTION_URL="https://mongodb.com" -DOTCOM_PRODUCTION_BUCKET=docs-mongodb-org-dotcomprd -DOTCOM_PREFIX=docs/mongoid -DOTCOM_STGPREFIX=docs-qa/mongoid - -# Parse our published-branches configuration file to get the name of -# the current "stable" branch. This is weird and dumb, yes. -STABLE_BRANCH=`grep 'manual' build/docs-tools/data/${PROJECT}-published-branches.yaml | cut -d ':' -f 2 | grep -Eo '[0-9a-z.]+'` - -.PHONY: help stage fake-deploy deploy deploy-search-index api-docs get-assets migrate clean - -help: ## Show this help message - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - @echo - @echo 'Variables' - @printf " \033[36m%-18s\033[0m %s\n" 'ARGS' 'Arguments to pass to mut-publish' - -html: migrate ## Builds this branch's HTML under build//html - giza make html - -## Migrate the files from the driver repo and build the dirhtml for publishing -## In course of building, will use yard to build API docs (takes a while) -# you must install yard -# generate the api docs from the mongoid project and output to the build dir - -debug: - @echo ${DOTCOM_PRODUCTION_BUCKET} - -publish: migrate ## Builds this branch's publishable HTML and other artifacts under build/public - giza make publish - @echo "Making api directory in /build/public/${GIT_BRANCH}" - if [ -d build/public/${GIT_BRANCH}/api ]; then rm -rf build/public/${GIT_BRANCH}/api ; fi; - mkdir build/public/${GIT_BRANCH}/api - - yard doc ${SOURCE_FILE_DIR} --exclude ${SOURCE_FILE_DIR}/spec --exclude ${SOURCE_FILE_DIR}/perf --readme ${SOURCE_FILE_DIR}/README.md -o build/public/${GIT_BRANCH}/api/ - if [ ${GIT_BRANCH} = master ]; then mut-redirects config/redirects -o build/public/.htaccess; fi - -stage: ## Host online for review - mut-publish build/${GIT_BRANCH}/html ${DOTCOM_STAGING_BUCKET} --prefix=${DOTCOM_STGPREFIX} --stage ${ARGS} - @echo "Hosted at ${DOTCOM_STAGING_URL}/${DOTCOM_STGPREFIX}/${USER}/${GIT_BRANCH}/index.html" - - - -fake-deploy: build/public/${GIT_BRANCH} ## Create a fake deployment in the staging bucket - mut-publish build/public ${DOTCOM_STAGING_BUCKET} --prefix=${DOTCOM_STGPREFIX} --deploy --verbose ${ARGS} - @echo "Hosted at ${DOTCOM_STAGING_URL}/${DOTCOM_STGPREFIX}/${GIT_BRANCH}/index.html" - - - -deploy: build/public/${GIT_BRANCH} ## Deploy to the production bucket - mut-publish build/public/ ${DOTCOM_PRODUCTION_BUCKET} --prefix=${DOTCOM_PREFIX} --deploy --redirects build/public/.htaccess ${ARGS} - @echo "Hosted at ${DOTCOM_PRODUCTION_URL}/${DOTCOM_PREFIX}/${GIT_BRANCH}" - - $(MAKE) deploy-search-index - -deploy-search-index: ## Update the search index for this branch - @echo "Building search index" - if [ ${STABLE_BRANCH} = ${GIT_BRANCH} ]; then \ - mut-index upload build/public/${GIT_BRANCH} -o ${PROJECT}-${GIT_BRANCH}.json -u ${PRODUCTION_URL}/${PROJECT}/${GIT_BRANCH} -b ${SEARCH_INDEX_BUCKET} -p search-indexes/prd -g -s --exclude build/public/${GIT_BRANCH}/api; \ - else \ - mut-index upload build/public/${GIT_BRANCH} -o ${PROJECT}-${GIT_BRANCH}.json -u ${PRODUCTION_URL}/${PROJECT}/${GIT_BRANCH} -b ${SEARCH_INDEX_BUCKET} -p search-indexes/prd -s --exclude build/public/${GIT_BRANCH}/api; \ - fi - -# in case you want to just generate the api-docs -# generate the api docs -# you must install yard -# generate the api docs from the mongoid project and output to the build dir - -api-docs: - @echo "Making api directory in /build/public/${GIT_BRANCH}" - if [ -d build/public/${GIT_BRANCH}/api ]; then rm -rf build/public/${GIT_BRANCH}/api ; fi; - mkdir build/public/${GIT_BRANCH}/api - - yard doc ${SOURCE_FILE_DIR} --exclude ${SOURCE_FILE_DIR}/spec --exclude ${SOURCE_FILE_DIR}/perf --readme ${SOURCE_FILE_DIR}/README.md -o build/public/${GIT_BRANCH}/api/ - - -migrate: get-assets - @echo "Making target source directory" - if [ -d ${TARGET_DIR} ]; then rm -rf ${TARGET_DIR} ; fi; - mkdir ${TARGET_DIR} - - - @echo "Copying over mongoid doc files" - cp -r ${SOURCE_FILE_DIR}/docs/* ${TARGET_DIR}/ - -# This gets the docs-tools and the mongoid docs from the mongoid repo. -# the assets are defined in the config/build_conf.yaml - -get-assets: - giza generate assets - -clean: - rm -rf build giza.log source-master diff --git a/conf-sitemap.xml b/conf-sitemap.xml deleted file mode 100644 index 1be77f4c..00000000 --- a/conf-sitemap.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index 023892ed..00000000 --- a/entrypoint.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -set -ex - -if test -n "$1"; then - exec "$@" -else - make publish -fi diff --git a/snooty.toml b/snooty.toml index 07458bfa..dd5e3a75 100644 --- a/snooty.toml +++ b/snooty.toml @@ -13,4 +13,9 @@ toc_landing_pages = [ rails-6-version = 6.0 rails-7-version = 7.1 odm = "Mongoid" +version = "9.0" +full-version = "{+version+}.2" ruby-driver = "Ruby driver" +language = "Ruby" +quickstart-app-name = "my-app" +feedback-widget-title = "Feedback" diff --git a/source/Makefile b/source/Makefile deleted file mode 100644 index d4bb2cbb..00000000 --- a/source/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/source/README.md b/source/README.md deleted file mode 100644 index a3e415fa..00000000 --- a/source/README.md +++ /dev/null @@ -1,14 +0,0 @@ -Mongoid Documentation -================================= - -This subdirectory contains the high-level driver documentation, including -tutorials and the reference. - -To build the documentation locally for review, install `sphinx` and -`sphinx-book-theme`, then execute `make html` in this directory: - - pip install 'sphinx<4.3' sphinx-book-theme - make html - -Note: sphinx 4.3 is currently breaking when trying to render Mongoid -documentation. diff --git a/source/conf.py b/source/conf.py deleted file mode 100644 index b634583a..00000000 --- a/source/conf.py +++ /dev/null @@ -1,57 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- Project information ----------------------------------------------------- - -project = 'Mongoid' -copyright = '2021, MongoDB' - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -source_suffix = { - '.txt': 'restructuredtext', -} - -html_theme = 'sphinx_book_theme' diff --git a/source/includes/quick-start/troubleshoot.rst b/source/includes/quick-start/troubleshoot.rst new file mode 100644 index 00000000..f23e5e43 --- /dev/null +++ b/source/includes/quick-start/troubleshoot.rst @@ -0,0 +1,6 @@ +.. note:: + + If you run into issues, ask for help in the + :community-forum:`MongoDB Community Forums <>` or submit feedback by using + the :guilabel:`{+feedback-widget-title+}` button in the upper right + corner of the page. diff --git a/source/meta/404.txt b/source/meta/404.txt deleted file mode 100644 index 4143ded8..00000000 --- a/source/meta/404.txt +++ /dev/null @@ -1,7 +0,0 @@ -:orphan: - -************** -File not found -************** - -The URL you requested does not exist or has been removed. diff --git a/source/quick-start/download-and-install.txt b/source/quick-start/download-and-install.txt new file mode 100644 index 00000000..08ccf073 --- /dev/null +++ b/source/quick-start/download-and-install.txt @@ -0,0 +1,95 @@ +.. _mongoid-qs-download-and-install: + +==================== +Download and Install +==================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: ruby framework, odm, code example + +Prerequisites +------------- + +To create the Quick Start application, you need the following software +installed in your development environment: + +- `Ruby `__ +- `RubyGems package manager `__ +- A terminal app and shell. For MacOS users, use Terminal or a similar app. + For Windows users, use PowerShell. + +Download and Install the Mongoid Gem +------------------------------------ + +Complete the following steps to install and add the {+odm+} gem +to your web application. + +.. procedure:: + :style: connected + + .. step:: Install {+odm+} + + Ensure that the version of {+odm+} you install is compatible with the + version of {+language+} installed on your operating system. To + learn which versions are compatible, see the + :ref:`mongoid-compatibility` page. + + Run the following command to install {+odm+}: + + .. code-block:: bash + + gem install mongoid + + In {+language+}, packages are called **gems**. + + When the installation completes, the command outputs the following + message: + + .. code-block:: none + :copyable: false + + Successfully installed mongoid-{+full-version} + Parsing documentation for mongoid-{+full-version} + Installing ri documentation for mongoid-{+full-version} + Done installing documentation for mongoid after 1 seconds + 1 gem installed + + .. step:: Create a Quick Start application directory + + Run the following commands to generate a new directory + called ``{+quickstart-app-name+}`` and enter it: + + .. code-block:: bash + + mkdir {+quickstart-app-name+} + cd {+quickstart-app-name+} + + .. step:: Create a ``Gemfile`` and add packages + + All {+language+} applications must have a ``Gemfile`` that lists + the required packages, or gems. Run the following command to + create a ``Gemfile`` in your application: + + .. code-block:: bash + + touch Gemfile + + Populate your ``Gemfile`` with the following list of gems: + + .. code-block:: ruby + + source 'https://rubygems.org' + + gem 'mongoid' + + After completing these steps, you have a new web application with + {+odm+} installed. + +.. In the :ref:`` section of the Quick Start, you can learn how +.. to install the specific gems needed for your preferred web framework. + +.. include:: /includes/quick-start/troubleshoot.rst diff --git a/source/reference/compatibility.txt b/source/reference/compatibility.txt index 5fa7e60c..659fae45 100644 --- a/source/reference/compatibility.txt +++ b/source/reference/compatibility.txt @@ -1,10 +1,8 @@ -.. _compatibility: +.. _mongoid-compatibility: -************* +============= Compatibility -************* - -.. default-domain:: mongodb +============= .. contents:: On this page :local: @@ -13,7 +11,7 @@ Compatibility :class: singlecol Ruby MongoDB Driver Compatibility -================================= +--------------------------------- The following compatibility table specifies the versions of `Ruby driver for MongoDB `_ @@ -53,7 +51,7 @@ specified Mongoid versions. Ruby Compatibility -================== +------------------ The following compatibility table specifies the versions of Ruby interpreters supported by Mongoid. "D" in a column means support for that Ruby version @@ -216,7 +214,7 @@ is deprecated. MongoDB Server Compatibility -============================ +---------------------------- The following compatibility table specifies the recommended version(s) of Mongoid for use with a specific version of MongoDB server. @@ -309,7 +307,7 @@ and will be removed in a next version. .. _rails-compatibility: Rails Compatibility -=================== +------------------- The following compatibility table specifies which versions of Ruby on Rails are supported by Mongoid. @@ -428,7 +426,7 @@ are supported by Mongoid. .. include:: /includes/unicode-ballot-x.rst Rails Frameworks Support ------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~ Ruby on Rails is comprised of a number of frameworks, which Mongoid attempts to provide compatibility with wherever possible. diff --git a/worker.sh b/worker.sh deleted file mode 100644 index c615eb59..00000000 --- a/worker.sh +++ /dev/null @@ -1 +0,0 @@ -"build-and-stage-next-gen" From 7bad830dd74a1ffe9a6b6c59207cfbba2f0a49fd Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 27 Sep 2024 14:32:40 -0400 Subject: [PATCH 007/113] wip --- source/quick-start/download-and-install.txt | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/source/quick-start/download-and-install.txt b/source/quick-start/download-and-install.txt index 08ccf073..c21e2766 100644 --- a/source/quick-start/download-and-install.txt +++ b/source/quick-start/download-and-install.txt @@ -52,9 +52,9 @@ to your web application. .. code-block:: none :copyable: false - Successfully installed mongoid-{+full-version} - Parsing documentation for mongoid-{+full-version} - Installing ri documentation for mongoid-{+full-version} + Successfully installed mongoid-{+full-version+} + Parsing documentation for mongoid-{+full-version+} + Installing ri documentation for mongoid-{+full-version+} Done installing documentation for mongoid after 1 seconds 1 gem installed @@ -78,18 +78,11 @@ to your web application. touch Gemfile - Populate your ``Gemfile`` with the following list of gems: - - .. code-block:: ruby - - source 'https://rubygems.org' - - gem 'mongoid' + In the TODO Connect to MongoDB section of the Quick Start, you can + learn which specific gems to add to the ``Gemfile`` depending on your + preferred web framework. After completing these steps, you have a new web application with {+odm+} installed. - -.. In the :ref:`` section of the Quick Start, you can learn how -.. to install the specific gems needed for your preferred web framework. .. include:: /includes/quick-start/troubleshoot.rst From 34f9ea95e83c04c9ee39002f7596522e06df491b Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 27 Sep 2024 14:55:53 -0400 Subject: [PATCH 008/113] adapt for sinatra --- snooty.toml | 2 +- source/index.txt | 2 +- ...uick-start.txt => quick-start-sinatra.txt} | 30 +++++++----- .../download-and-install.txt | 47 ++++++++++++++----- 4 files changed, 55 insertions(+), 26 deletions(-) rename source/{quick-start.txt => quick-start-sinatra.txt} (59%) rename source/{quick-start => quick-start-sinatra}/download-and-install.txt (61%) diff --git a/snooty.toml b/snooty.toml index dd5e3a75..695242b8 100644 --- a/snooty.toml +++ b/snooty.toml @@ -17,5 +17,5 @@ version = "9.0" full-version = "{+version+}.2" ruby-driver = "Ruby driver" language = "Ruby" -quickstart-app-name = "my-app" +quickstart-sinatra-app-name = "my-sinatra-app" feedback-widget-title = "Feedback" diff --git a/source/index.txt b/source/index.txt index 282139a0..244c7194 100644 --- a/source/index.txt +++ b/source/index.txt @@ -12,7 +12,7 @@ MongoDB in Ruby. To work with {+odm+} from the command line using .. toctree:: :titlesonly: - /quick-start + /quick-start-sinatra installation-configuration tutorials schema-configuration diff --git a/source/quick-start.txt b/source/quick-start-sinatra.txt similarity index 59% rename from source/quick-start.txt rename to source/quick-start-sinatra.txt index 1ab513c0..a0b526b4 100644 --- a/source/quick-start.txt +++ b/source/quick-start-sinatra.txt @@ -1,8 +1,8 @@ -.. _mongoid-quick-start: +.. _mongoid-quick-start-sinatra: -=========== -Quick Start -=========== +===================== +Quick Start (Sinatra) +===================== .. facet:: :name: genre @@ -20,7 +20,7 @@ Quick Start Overview -------- -This guide shows you how to use {+odm+} in a new web application, +This guide shows you how to use {+odm+} in a new Sinatra web application, connect to a MongoDB cluster hosted on MongoDB Atlas, and perform read and write operations on the data in your cluster. @@ -34,6 +34,12 @@ read and write operations on the data in your cluster. Ruby. By using {+odm+}, you can easily interact with your data and create flexible data models. +Sinatra is a Domain Specific Language (DSL) for creating web +applications in {+language+}. Sinatra applications are simple to set up +and can provide faster request processing than other frameworks. + +.. TODO .. tip:: If you prefer to use Rails as your web framework, see the Quick Start (Rails) guide. + MongoDB Atlas is a fully managed cloud database service that hosts your MongoDB deployments. You can create your own free (no credit card required) MongoDB Atlas deployment by following the steps in this guide. @@ -48,11 +54,11 @@ that connects to a MongoDB deployment. .. toctree:: - /quick-start/download-and-install/ + /quick-start-sinatra/download-and-install/ -.. /quick-start/create-a-deployment/ -.. /quick-start/create-a-connection-string/ -.. /quick-start/configure-mongodb/ -.. /quick-start/view-data/ -.. /quick-start/write-data/ -.. /quick-start/next-steps/ +.. /quick-start-sinatra/create-a-deployment/ +.. /quick-start-sinatra/create-a-connection-string/ +.. /quick-start-sinatra/configure-mongodb/ +.. /quick-start-sinatra/view-data/ +.. /quick-start-sinatra/write-data/ +.. /quick-start-sinatra/next-steps/ diff --git a/source/quick-start/download-and-install.txt b/source/quick-start-sinatra/download-and-install.txt similarity index 61% rename from source/quick-start/download-and-install.txt rename to source/quick-start-sinatra/download-and-install.txt index c21e2766..e0ae25a0 100644 --- a/source/quick-start/download-and-install.txt +++ b/source/quick-start-sinatra/download-and-install.txt @@ -14,8 +14,8 @@ Download and Install Prerequisites ------------- -To create the Quick Start application, you need the following software -installed in your development environment: +To create the Quick Start application by using Sinatra, you need the +following software installed in your development environment: - `Ruby `__ - `RubyGems package manager `__ @@ -61,28 +61,51 @@ to your web application. .. step:: Create a Quick Start application directory Run the following commands to generate a new directory - called ``{+quickstart-app-name+}`` and enter it: + called ``{+quickstart-sinatra-app-name+}`` and enter it: .. code-block:: bash - mkdir {+quickstart-app-name+} - cd {+quickstart-app-name+} + mkdir {+quickstart-sinatra-app-name+} + cd {+quickstart-sinatra-app-name+} - .. step:: Create a ``Gemfile`` and add packages + .. step:: Create a ``Gemfile`` and add gems All {+language+} applications must have a ``Gemfile`` that lists - the required packages, or gems. Run the following command to - create a ``Gemfile`` in your application: + the required gems. Run the following command to create a + ``Gemfile`` in your application: .. code-block:: bash touch Gemfile - In the TODO Connect to MongoDB section of the Quick Start, you can - learn which specific gems to add to the ``Gemfile`` depending on your - preferred web framework. + Paste the following content into your ``Gemfile`` to add the + required gems: - After completing these steps, you have a new web application with + .. code-block:: ruby + + source 'https://rubygems.org' + + gem 'sinatra' + gem 'mongoid' + gem 'rackup' + + The ``rackup`` gem provides a web server interface for your + application. + + .. step:: Install gems + + Run the following command to install the list of gems into your + application: + + .. code-block:: bash + + gem install bundler + bundle install + + When the command runs successfully, you should see output in your + shell that contains a ``Bundle complete!`` message. + + After completing these steps, you have a new Sinatra web application with {+odm+} installed. .. include:: /includes/quick-start/troubleshoot.rst From 7bd9d5143a42e3ecede6ecd596ea695ef0fbc1d0 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 27 Sep 2024 14:58:41 -0400 Subject: [PATCH 009/113] vale fix --- source/quick-start-sinatra/download-and-install.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/quick-start-sinatra/download-and-install.txt b/source/quick-start-sinatra/download-and-install.txt index e0ae25a0..766d52f4 100644 --- a/source/quick-start-sinatra/download-and-install.txt +++ b/source/quick-start-sinatra/download-and-install.txt @@ -102,8 +102,8 @@ to your web application. gem install bundler bundle install - When the command runs successfully, you should see output in your - shell that contains a ``Bundle complete!`` message. + When the command runs successfully, the output in your + shell contains a ``Bundle complete!`` message. After completing these steps, you have a new Sinatra web application with {+odm+} installed. From 17a43874c724f96ebebc1b82d85eaea90f7f26b1 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 27 Sep 2024 15:01:38 -0400 Subject: [PATCH 010/113] snooty landing page --- snooty.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snooty.toml b/snooty.toml index 695242b8..ca122e1b 100644 --- a/snooty.toml +++ b/snooty.toml @@ -6,7 +6,7 @@ intersphinx = [ "https://www.mongodb.com/docs/manual/objects.inv", ] toc_landing_pages = [ - "/quick-start" + "/quick-start-sinatra" ] [constants] From a0f2f91089555caa5f9f76914ea9c72a35007d41 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 30 Sep 2024 12:56:11 -0400 Subject: [PATCH 011/113] MW PR fixes 1 --- source/quick-start-sinatra.txt | 2 +- .../download-and-install.txt | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index a0b526b4..60f5bd32 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -34,7 +34,7 @@ read and write operations on the data in your cluster. Ruby. By using {+odm+}, you can easily interact with your data and create flexible data models. -Sinatra is a Domain Specific Language (DSL) for creating web +Sinatra is a domain-specific language (DSL) for creating web applications in {+language+}. Sinatra applications are simple to set up and can provide faster request processing than other frameworks. diff --git a/source/quick-start-sinatra/download-and-install.txt b/source/quick-start-sinatra/download-and-install.txt index 766d52f4..2aff7826 100644 --- a/source/quick-start-sinatra/download-and-install.txt +++ b/source/quick-start-sinatra/download-and-install.txt @@ -17,16 +17,18 @@ Prerequisites To create the Quick Start application by using Sinatra, you need the following software installed in your development environment: -- `Ruby `__ -- `RubyGems package manager `__ +- `Ruby `__. +- `RubyGems package manager `__. - A terminal app and shell. For MacOS users, use Terminal or a similar app. For Windows users, use PowerShell. -Download and Install the Mongoid Gem ------------------------------------- +Download and Install the {+odm+} and Framework Gems +--------------------------------------------------- -Complete the following steps to install and add the {+odm+} gem -to your web application. +In {+language+}, packages are called **gems**. + +Complete the following steps to install and add the {+odm+} and Sinatra +gems to your web application. .. procedure:: :style: connected @@ -38,14 +40,12 @@ to your web application. learn which versions are compatible, see the :ref:`mongoid-compatibility` page. - Run the following command to install {+odm+}: + Run the following command to install the {+odm+} gem: .. code-block:: bash gem install mongoid - In {+language+}, packages are called **gems**. - When the installation completes, the command outputs the following message: @@ -78,7 +78,7 @@ to your web application. touch Gemfile - Paste the following content into your ``Gemfile`` to add the + Paste the following content into the ``Gemfile`` to add the required gems: .. code-block:: ruby @@ -94,7 +94,7 @@ to your web application. .. step:: Install gems - Run the following command to install the list of gems into your + Run the following command to install the specified gems into your application: .. code-block:: bash @@ -103,7 +103,8 @@ to your web application. bundle install When the command runs successfully, the output in your - shell contains a ``Bundle complete!`` message. + shell contains a ``Bundle complete!`` message and describes the + number of new gems installed. After completing these steps, you have a new Sinatra web application with {+odm+} installed. From a638deba47aa28bff79ca97a2b89ccaec98d8a2c Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 30 Sep 2024 14:03:04 -0400 Subject: [PATCH 012/113] DOCSP-42733: atlas prep qs --- snooty.toml | 1 + .../atlas_connection_select_cluster.png | Bin 0 -> 324564 bytes .../includes/quick-start/create-cxn-str.rst | 51 ++++++++++++++++++ .../quick-start/create-deployment.rst | 22 ++++++++ source/quick-start-sinatra.txt | 4 +- .../create-a-connection-string.txt | 7 +++ .../create-a-deployment.txt | 7 +++ 7 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 source/includes/figures/atlas_connection_select_cluster.png create mode 100644 source/includes/quick-start/create-cxn-str.rst create mode 100644 source/includes/quick-start/create-deployment.rst create mode 100644 source/quick-start-sinatra/create-a-connection-string.txt create mode 100644 source/quick-start-sinatra/create-a-deployment.txt diff --git a/snooty.toml b/snooty.toml index ca122e1b..90142360 100644 --- a/snooty.toml +++ b/snooty.toml @@ -19,3 +19,4 @@ ruby-driver = "Ruby driver" language = "Ruby" quickstart-sinatra-app-name = "my-sinatra-app" feedback-widget-title = "Feedback" +server-manual = "Server manual" diff --git a/source/includes/figures/atlas_connection_select_cluster.png b/source/includes/figures/atlas_connection_select_cluster.png new file mode 100644 index 0000000000000000000000000000000000000000..e072febf890d3b9b0c14386453bae422f43edbb7 GIT binary patch literal 324564 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 z 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�n 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

DH-_ZBl^9iQ=TIMJZ!iCXxEY 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

_+ec`5tIy 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 +in the {+server-manual+}. + +.. procedure:: + :style: connected + + .. step:: Find your MongoDB Atlas connection string + + To retrieve your connection string for the deployment that + you created in the previous step, log in to your Atlas account. + Then, navigate to the :guilabel:`Database` section and click the + :guilabel:`Connect` button for your new deployment. + + .. figure:: /includes/figures/atlas_connection_select_cluster.png + :alt: The connect button in the clusters section of the Atlas UI + + Proceed to the :guilabel:`Connect your application` section. Select + **{+language+}** from the :guilabel:`Driver` selection menu and + the most recent {+ruby-driver+} version from the + :guilabel:`Version` selection menu. + + Deselect the :guilabel:`View full code sample` option to view only + the connection string. + + .. step:: Copy your connection string + + Click the button on the right of the connection string to copy it + to your clipboard. + + .. step:: Update the placeholders + + Paste the connection string into a file in your preferred text editor + and replace the ```` and ```` placeholders with + your database user's username and password. + + Save this file to a safe location for use in the next step. + +After completing these steps, you have a connection string that +contains your database username and password. + +.. include:: /includes/quick-start/troubleshoot.rst \ No newline at end of file diff --git a/source/includes/quick-start/create-deployment.rst b/source/includes/quick-start/create-deployment.rst new file mode 100644 index 00000000..35d62721 --- /dev/null +++ b/source/includes/quick-start/create-deployment.rst @@ -0,0 +1,22 @@ +You can create a free tier MongoDB deployment on MongoDB Atlas +to store and manage your data. MongoDB Atlas hosts and manages +your MongoDB database in the cloud. + +.. procedure:: + :style: connected + + .. step:: Create a free MongoDB deployment on Atlas + + Complete the :atlas:`Get Started with Atlas ` + guide to set up a new Atlas account and load sample data into a new free + tier MongoDB deployment. + + .. step:: Save your credentials + + After you create your database user, save that user's + username and password to a safe location for use in an upcoming step. + +After completing these steps, you have a new free tier MongoDB deployment on +Atlas, database user credentials, and sample data loaded into your database. + +.. include:: /includes/quick-start/troubleshoot.rst diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index 60f5bd32..176b4efd 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -55,9 +55,9 @@ that connects to a MongoDB deployment. .. toctree:: /quick-start-sinatra/download-and-install/ + /quick-start-sinatra/create-a-deployment/ + /quick-start-sinatra/create-a-connection-string/ -.. /quick-start-sinatra/create-a-deployment/ -.. /quick-start-sinatra/create-a-connection-string/ .. /quick-start-sinatra/configure-mongodb/ .. /quick-start-sinatra/view-data/ .. /quick-start-sinatra/write-data/ diff --git a/source/quick-start-sinatra/create-a-connection-string.txt b/source/quick-start-sinatra/create-a-connection-string.txt new file mode 100644 index 00000000..cb44afc2 --- /dev/null +++ b/source/quick-start-sinatra/create-a-connection-string.txt @@ -0,0 +1,7 @@ +.. _mongoid-quick-start-sinatra-create-cxn-str: + +========================== +Create a Connection String +========================== + +.. include:: /includes/quick-start/create-cxn-str.rst \ No newline at end of file diff --git a/source/quick-start-sinatra/create-a-deployment.txt b/source/quick-start-sinatra/create-a-deployment.txt new file mode 100644 index 00000000..758a7807 --- /dev/null +++ b/source/quick-start-sinatra/create-a-deployment.txt @@ -0,0 +1,7 @@ +.. _mongoid-quick-start-sinatra-create-deployment: + +=========================== +Create a MongoDB Deployment +=========================== + +.. include:: /includes/quick-start/create-deployment.rst From 44559fb4725aab41053a13530796185106122cc2 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 30 Sep 2024 15:40:19 -0400 Subject: [PATCH 013/113] MW PR fixes 1 --- source/includes/quick-start/create-cxn-str.rst | 4 ++-- source/includes/quick-start/create-deployment.rst | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/includes/quick-start/create-cxn-str.rst b/source/includes/quick-start/create-cxn-str.rst index 0ea70493..2708e1c6 100644 --- a/source/includes/quick-start/create-cxn-str.rst +++ b/source/includes/quick-start/create-cxn-str.rst @@ -1,7 +1,7 @@ You can connect to your MongoDB deployment by providing a **connection URI**, also called a *connection string*, which -instructs {+odm+} on how to connect to a MongoDB deployment -and how to behave while connected. +tells {+odm+} how to connect to a MongoDB deployment and behave while +connected. The connection string includes the hostname or IP address and port of your deployment, the authentication mechanism, user credentials diff --git a/source/includes/quick-start/create-deployment.rst b/source/includes/quick-start/create-deployment.rst index 35d62721..30f49201 100644 --- a/source/includes/quick-start/create-deployment.rst +++ b/source/includes/quick-start/create-deployment.rst @@ -1,4 +1,4 @@ -You can create a free tier MongoDB deployment on MongoDB Atlas +You can create a free-tier MongoDB deployment on MongoDB Atlas to store and manage your data. MongoDB Atlas hosts and manages your MongoDB database in the cloud. @@ -8,15 +8,15 @@ your MongoDB database in the cloud. .. step:: Create a free MongoDB deployment on Atlas Complete the :atlas:`Get Started with Atlas ` - guide to set up a new Atlas account and load sample data into a new free - tier MongoDB deployment. + guide to set up a new Atlas account and load sample data into a + new free-tier MongoDB deployment. .. step:: Save your credentials After you create your database user, save that user's username and password to a safe location for use in an upcoming step. -After completing these steps, you have a new free tier MongoDB deployment on +After completing these steps, you have a new free-tier MongoDB deployment on Atlas, database user credentials, and sample data loaded into your database. .. include:: /includes/quick-start/troubleshoot.rst From f1e19b8cd5ccb77042660ef984df269ae764625e Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 30 Sep 2024 16:45:53 -0400 Subject: [PATCH 014/113] DOCSP-42735: configure cxn --- source/quick-start-sinatra.txt | 2 +- .../quick-start-sinatra/configure-mongodb.txt | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 source/quick-start-sinatra/configure-mongodb.txt diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index 176b4efd..11814e03 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -57,8 +57,8 @@ that connects to a MongoDB deployment. /quick-start-sinatra/download-and-install/ /quick-start-sinatra/create-a-deployment/ /quick-start-sinatra/create-a-connection-string/ + /quick-start-sinatra/configure-mongodb/ -.. /quick-start-sinatra/configure-mongodb/ .. /quick-start-sinatra/view-data/ .. /quick-start-sinatra/write-data/ .. /quick-start-sinatra/next-steps/ diff --git a/source/quick-start-sinatra/configure-mongodb.txt b/source/quick-start-sinatra/configure-mongodb.txt new file mode 100644 index 00000000..905cac84 --- /dev/null +++ b/source/quick-start-sinatra/configure-mongodb.txt @@ -0,0 +1,54 @@ +.. _mongoid-quick-start-sinatra-connect-to-mongodb: + +================================= +Configure Your MongoDB Connection +================================= + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: test connection, runnable, code example + +.. procedure:: + :style: connected + + .. step:: Specify target database in connection string + + When connecting to an Atlas cluster, you must specify the name of the + database that you want to interact with as the default database in your + connection string. + + We want to access the ``sample_restaurants`` database for this tutorial, + which you can add to your connection string **after the hostname**. + + For example, if the connection string for your Atlas cluster is + ``"mongodb+srv://user0:pass123@mongo0.example.com/"``, you can + specify the target database as shown in the following code: + + .. code-block:: none + + mongodb+srv://user0:pass123@mongo0.example.com/sample_restaurants + + .. step:: Specify connection + + In your project, create the ``config`` directory, then create a + file in this directory called ``mongoid.yml``. + + Paste the following configuration into the file, making sure to + replace the ```` placeholder with your connection + string that contains the target database: + + .. code-block:: yaml + :emphasize-lines: 4 + + development: + clients: + default: + uri: + +After completing these steps, your Sinatra web application is ready to +connect to MongoDB. + +.. include:: /includes/quick-start/troubleshoot.rst From a3997e002fd1180d01678760c4af530460efbc12 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 30 Sep 2024 16:54:01 -0400 Subject: [PATCH 015/113] small fix - vale --- source/quick-start-sinatra/configure-mongodb.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/source/quick-start-sinatra/configure-mongodb.txt b/source/quick-start-sinatra/configure-mongodb.txt index 905cac84..869458ba 100644 --- a/source/quick-start-sinatra/configure-mongodb.txt +++ b/source/quick-start-sinatra/configure-mongodb.txt @@ -16,13 +16,11 @@ Configure Your MongoDB Connection .. step:: Specify target database in connection string - When connecting to an Atlas cluster, you must specify the name of the - database that you want to interact with as the default database in your - connection string. + When connecting to an Atlas cluster, you must specify the database that + you want to interact with as the default database in your connection string. + You must add the database name to your connection string **after the hostname**. - We want to access the ``sample_restaurants`` database for this tutorial, - which you can add to your connection string **after the hostname**. - + We want to access the ``sample_restaurants`` database for this tutorial. For example, if the connection string for your Atlas cluster is ``"mongodb+srv://user0:pass123@mongo0.example.com/"``, you can specify the target database as shown in the following code: From 72559280dfab145f45f1f89e1eebd8be7b47c7fc Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 1 Oct 2024 10:21:30 -0400 Subject: [PATCH 016/113] JS PR fixes 1 --- source/quick-start-sinatra/configure-mongodb.txt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/source/quick-start-sinatra/configure-mongodb.txt b/source/quick-start-sinatra/configure-mongodb.txt index 869458ba..716104d1 100644 --- a/source/quick-start-sinatra/configure-mongodb.txt +++ b/source/quick-start-sinatra/configure-mongodb.txt @@ -20,10 +20,8 @@ Configure Your MongoDB Connection you want to interact with as the default database in your connection string. You must add the database name to your connection string **after the hostname**. - We want to access the ``sample_restaurants`` database for this tutorial. - For example, if the connection string for your Atlas cluster is - ``"mongodb+srv://user0:pass123@mongo0.example.com/"``, you can - specify the target database as shown in the following code: + The following example specifies the ``sample_restaurants`` target database + in a sample connection string: .. code-block:: none @@ -31,12 +29,12 @@ Configure Your MongoDB Connection .. step:: Specify connection - In your project, create the ``config`` directory, then create a - file in this directory called ``mongoid.yml``. + At the root level of your project, create a ``config`` directory. + Then, create a file in this directory called ``mongoid.yml``. - Paste the following configuration into the file, making sure to - replace the ```` placeholder with your connection - string that contains the target database: + Paste the following configuration into the ``mongoid.yml`` file, + making sure to replace the ```` placeholder + with your connection string that references the target database: .. code-block:: yaml :emphasize-lines: 4 From 28a8d69d0c80deff025bc484fed6923d4221b3da Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 1 Oct 2024 12:52:29 -0400 Subject: [PATCH 017/113] DOCSP-44008: read/write sinatra quickstart --- .../figures/quickstart-sinatra-list.png | Bin 0 -> 206814 bytes source/quick-start-sinatra.txt | 7 +- source/quick-start-sinatra/next-steps.txt | 31 ++++ source/quick-start-sinatra/view-data.txt | 150 ++++++++++++++++++ source/quick-start-sinatra/write-data.txt | 43 +++++ 5 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 source/includes/figures/quickstart-sinatra-list.png create mode 100644 source/quick-start-sinatra/next-steps.txt create mode 100644 source/quick-start-sinatra/view-data.txt create mode 100644 source/quick-start-sinatra/write-data.txt diff --git a/source/includes/figures/quickstart-sinatra-list.png b/source/includes/figures/quickstart-sinatra-list.png new file mode 100644 index 0000000000000000000000000000000000000000..8adc6c1ca4003d497dfb184229da083b801e8530 GIT binary patch literal 206814 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 literal 0 HcmV?d00001 diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index 11814e03..a352db94 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -58,7 +58,6 @@ that connects to a MongoDB deployment. /quick-start-sinatra/create-a-deployment/ /quick-start-sinatra/create-a-connection-string/ /quick-start-sinatra/configure-mongodb/ - -.. /quick-start-sinatra/view-data/ -.. /quick-start-sinatra/write-data/ -.. /quick-start-sinatra/next-steps/ + /quick-start-sinatra/view-data/ + /quick-start-sinatra/write-data/ + /quick-start-sinatra/next-steps/ diff --git a/source/quick-start-sinatra/next-steps.txt b/source/quick-start-sinatra/next-steps.txt new file mode 100644 index 00000000..fbe68ecc --- /dev/null +++ b/source/quick-start-sinatra/next-steps.txt @@ -0,0 +1,31 @@ +.. _mongoid-quick-start-sinatra-next-steps: + +========== +Next Steps +========== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: learn more + +Congratulations on completing the Quick Start tutorial for Sinatra! + +After you complete these steps, you have a Sinatra web application that +uses {+odm+} to connect to your MongoDB deployment, run a query on +the sample data, and render retrieved results. + +.. TODO You can download the completed web application project by cloning the +.. `mongoid-quickstart <>`__ +.. GitHub repository. + +.. TODO Learn more about {+odm+} features from the following resources: + +.. - :ref:`mongoid-fundamentals-connection`: learn how to configure your MongoDB +.. connection. +.. +.. - :ref:`mongoid-usage-examples`: see code examples of frequently used MongoDB +.. operations. + diff --git a/source/quick-start-sinatra/view-data.txt b/source/quick-start-sinatra/view-data.txt new file mode 100644 index 00000000..56dff21f --- /dev/null +++ b/source/quick-start-sinatra/view-data.txt @@ -0,0 +1,150 @@ +.. _mongoid-quick-start-sinatra-view-data: + +================= +View MongoDB Data +================= + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: test connection, runnable, code example + +.. procedure:: + :style: connected + + .. step:: Create an application file + + At the root level of your project, create a file called ``app.rb``. + Paste the following contents into the ``app.rb`` file to + load the necessary gems and your configuration file: + + .. code-block:: ruby + + require 'sinatra' + require 'mongoid' + + Mongoid.load!(File.join(File.dirname(__FILE__), 'config', 'mongoid.yml')) + + .. step:: Create a data model + + In the ``app.rb`` file, create a model called ``Restaurant`` + to represent data from the sample ``restaurants`` collection in + your MongoDB database: + + .. code-block:: ruby + + class Restaurant + include Mongoid::Document + + field :name, type: String + field :cuisine, type: String + field :borough, type: String + + end + + .. step:: Generate a view + + To display your data in a specified way by using HTML, you can + create a **view**. + + At the root level of your project, create a directory + called ``views``. Then, create a file called + ``list_restaurants.erb``. Paste the following code into the + ``list_restaurants.erb`` file: + + .. code-block:: html + + + + + Restaurants List + + +

Restaurants List

+ + + + + + + <% @restaurants.each do |restaurant| %> + + + + + + <% end %> +
NameCuisineBorough
<%= restaurant.name %><%= restaurant.cuisine %><%= restaurant.borough %>
+ + + + .. step:: Add a web route + + In the ``app.rb`` file, add a ``get`` route called + ``list_restaurants``, as shown in the following code: + + .. code-block:: ruby + + get '/list_restaurants' do + @restaurants = Restaurant + .where(name: /earth/i) + + erb :list_restaurants + end + + This route retrieves restaurant documents in which the value of + the ``name`` field contains the string ``"earth"``. The route + uses the ``list_restaurants`` view to render the results. + + .. _mongoid-quick-start-sinatra-json: + + .. step:: (Optional) View your results as JSON documents + + Instead of generating a view to render your results, you can + use the ``to_json()`` method to display your results in JSON + format. + + Replace the ``list_restaurants`` route with the following code to + retrieve results and return them as JSON documents: + + .. code-block:: php + + get '/list_restaurants' do + restaurants = Restaurant + .where(name: /earth/i) + + restaurants.to_json + end + + .. step:: Start your Sinatra application + + Run the following command from the application root directory + to start your {+language+} web server: + + .. code-block:: bash + + bundle exec ruby app.rb + + After the server starts, it outputs the following message + indicating that the application is running on port ``4567``: + + .. code-block:: none + :copyable: false + + [2024-10-01 12:36:49] INFO WEBrick 1.8.2 + [2024-10-01 12:36:49] INFO ruby 3.2.5 (2024-07-26) [arm64-darwin23] + == Sinatra (v4.0.0) has taken the stage on 4567 for development with backup from WEBrick + [2024-10-01 12:36:49] INFO WEBrick::HTTPServer#start: pid=79176 port=4567 + + .. step:: View the restaurant data + + Open the URL http://localhost:4567/list_restaurants in your web browser. + The page shows a list of restaurants and details about each of + them: + + .. figure:: /includes/figures/quickstart-sinatra-list.png + :alt: The rendered list of restaurants + +.. include:: /includes/quick-start/troubleshoot.rst diff --git a/source/quick-start-sinatra/write-data.txt b/source/quick-start-sinatra/write-data.txt new file mode 100644 index 00000000..198953ad --- /dev/null +++ b/source/quick-start-sinatra/write-data.txt @@ -0,0 +1,43 @@ +.. _mongoid-quick-start-sinatra-write-data: + +===================== +Write Data to MongoDB +===================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: test connection, runnable, code example + +.. procedure:: + :style: connected + + .. step:: Add a web route to insert data + + In the ``app.rb`` file, add a ``post`` route called + ``add_restaurant``, as shown in the following code: + + .. code-block:: ruby + + post '/add_restaurant' do + Restaurant.create!(params[:restaurant]) + end + + .. step:: Post a request to create a restaurant entry + + Send a ``Restaurant`` instance to the ``add_restaurant`` endpoint + by running the following command in your shell: + + .. code-block:: bash + + curl -d 'restaurant[name]=Good+Earth+Cafe&restaurant[cuisine]=Cafe&restaurant[borough]=Queens' http://localhost:4567/add_restaurant + + .. step:: View the data + + Refresh http://localhost:4567/list_restaurants in your web browser + to view the new restaurant entry that you submitted. The inserted + restaurant appears at the bottom of the list. + +.. include:: /includes/quick-start/troubleshoot.rst From 04fdbf2151e71491ed5d3fc0d0c22088d6793c09 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 1 Oct 2024 13:00:36 -0400 Subject: [PATCH 018/113] fixes --- source/quick-start-sinatra.txt | 2 +- source/quick-start-sinatra/view-data.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index a352db94..5b9e69c4 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -9,7 +9,7 @@ Quick Start (Sinatra) :values: tutorial .. meta:: - :keywords: php framework, odm + :keywords: ruby framework, odm .. contents:: On this page :local: diff --git a/source/quick-start-sinatra/view-data.txt b/source/quick-start-sinatra/view-data.txt index 56dff21f..2bfa35e1 100644 --- a/source/quick-start-sinatra/view-data.txt +++ b/source/quick-start-sinatra/view-data.txt @@ -109,7 +109,7 @@ View MongoDB Data Replace the ``list_restaurants`` route with the following code to retrieve results and return them as JSON documents: - .. code-block:: php + .. code-block:: ruby get '/list_restaurants' do restaurants = Restaurant From b7de8b0cd19b32f7948bf6205dce5569a223bea1 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 1 Oct 2024 13:29:18 -0400 Subject: [PATCH 019/113] NR PR fixes 1 --- source/quick-start-sinatra/next-steps.txt | 4 ++-- source/quick-start-sinatra/view-data.txt | 10 +++++----- source/quick-start-sinatra/write-data.txt | 7 +++++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/source/quick-start-sinatra/next-steps.txt b/source/quick-start-sinatra/next-steps.txt index fbe68ecc..d2c03d89 100644 --- a/source/quick-start-sinatra/next-steps.txt +++ b/source/quick-start-sinatra/next-steps.txt @@ -23,9 +23,9 @@ the sample data, and render retrieved results. .. TODO Learn more about {+odm+} features from the following resources: -.. - :ref:`mongoid-fundamentals-connection`: learn how to configure your MongoDB +.. - :ref:`mongoid-fundamentals-connection`: Learn how to configure your MongoDB .. connection. .. -.. - :ref:`mongoid-usage-examples`: see code examples of frequently used MongoDB +.. - :ref:`mongoid-usage-examples`: See code examples of frequently used MongoDB .. operations. diff --git a/source/quick-start-sinatra/view-data.txt b/source/quick-start-sinatra/view-data.txt index 2bfa35e1..8498eb4f 100644 --- a/source/quick-start-sinatra/view-data.txt +++ b/source/quick-start-sinatra/view-data.txt @@ -31,7 +31,7 @@ View MongoDB Data In the ``app.rb`` file, create a model called ``Restaurant`` to represent data from the sample ``restaurants`` collection in - your MongoDB database: + the ``sample_restaurants`` database: .. code-block:: ruby @@ -46,8 +46,8 @@ View MongoDB Data .. step:: Generate a view - To display your data in a specified way by using HTML, you can - create a **view**. + Create a **view** to display your data in a specified way by using + HTML and {+language+}. At the root level of your project, create a directory called ``views``. Then, create a file called @@ -106,8 +106,8 @@ View MongoDB Data use the ``to_json()`` method to display your results in JSON format. - Replace the ``list_restaurants`` route with the following code to - retrieve results and return them as JSON documents: + Replace the ``list_restaurants`` route in the ``app.rb`` file with + the following code to return the results as JSON documents: .. code-block:: ruby diff --git a/source/quick-start-sinatra/write-data.txt b/source/quick-start-sinatra/write-data.txt index 198953ad..5eebb2af 100644 --- a/source/quick-start-sinatra/write-data.txt +++ b/source/quick-start-sinatra/write-data.txt @@ -28,11 +28,14 @@ Write Data to MongoDB .. step:: Post a request to create a restaurant entry Send a ``Restaurant`` instance to the ``add_restaurant`` endpoint - by running the following command in your shell: + by running the following command from the application root + directory: .. code-block:: bash - curl -d 'restaurant[name]=Good+Earth+Cafe&restaurant[cuisine]=Cafe&restaurant[borough]=Queens' http://localhost:4567/add_restaurant + curl -d \ + 'restaurant[name]=Good+Earth+Cafe&restaurant[cuisine]=Cafe&restaurant[borough]=Queens' \ + http://localhost:4567/add_restaurant .. step:: View the data From e5fcf1a15dc26141ffd63bd52da02f797e91d2d2 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 14 Oct 2024 15:12:27 -0400 Subject: [PATCH 020/113] DOCSP-43961: rails qs --- snooty.toml | 1 + .../figures/quickstart-rails-list.png | Bin 0 -> 189443 bytes source/index.txt | 1 + source/quick-start-rails.txt | 75 +++++++++++ .../quick-start-rails/configure-mongodb.txt | 62 +++++++++ .../create-a-connection-string.txt | 7 + .../quick-start-rails/create-a-deployment.txt | 7 + .../download-and-install.txt | 120 ++++++++++++++++++ source/quick-start-rails/next-steps.txt | 32 +++++ source/quick-start-rails/view-data.txt | 90 +++++++++++++ source/quick-start-rails/write-data.txt | 41 ++++++ .../download-and-install.txt | 4 +- source/tutorials/getting-started-rails6.txt | 63 ++++----- source/tutorials/getting-started-rails7.txt | 13 +- 14 files changed, 471 insertions(+), 45 deletions(-) create mode 100644 source/includes/figures/quickstart-rails-list.png create mode 100644 source/quick-start-rails.txt create mode 100644 source/quick-start-rails/configure-mongodb.txt create mode 100644 source/quick-start-rails/create-a-connection-string.txt create mode 100644 source/quick-start-rails/create-a-deployment.txt create mode 100644 source/quick-start-rails/download-and-install.txt create mode 100644 source/quick-start-rails/next-steps.txt create mode 100644 source/quick-start-rails/view-data.txt create mode 100644 source/quick-start-rails/write-data.txt diff --git a/snooty.toml b/snooty.toml index 90142360..ba1c23ea 100644 --- a/snooty.toml +++ b/snooty.toml @@ -18,5 +18,6 @@ full-version = "{+version+}.2" ruby-driver = "Ruby driver" language = "Ruby" quickstart-sinatra-app-name = "my-sinatra-app" +quickstart-rails-app-name = "my-rails-app" feedback-widget-title = "Feedback" server-manual = "Server manual" diff --git a/source/includes/figures/quickstart-rails-list.png b/source/includes/figures/quickstart-rails-list.png new file mode 100644 index 0000000000000000000000000000000000000000..e6405847a480a0bc51b365d021ce85b884b01429 GIT binary patch literal 189443 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
TC|VjA#s^j5@7& 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. + +{+odm+} is an Object-Document Mapper (ODM) framework for MongoDB in +Ruby. By using {+odm+}, you can easily interact with your data and +create flexible data models. + +Ruby on Rails, or Rails, is a web application framework for +{+language+}. Rails applications use a model-view-controller (MVC) +architecture that allows you to easily control how your data is +modeled and displayed. {+odm+} replaces Rails' default +``ActiveRecord`` adapter for data modeling. + +To learn more about Ruby on Rails, see the `Getting Started +with Rails `__ +guide in the Rails documentation. + +.. tip:: Other Framework Tutorials + + If you prefer to use Rails 6 to build your application, see the + :ref:`mongoid-getting-started-rails-6` guide. + + If you prefer to use Sinatra as your web framework, see the + :ref:`mongoid-quick-start-sinatra` guide. + +MongoDB Atlas is a fully managed cloud database service that hosts your +MongoDB deployments. You can create your own free (no credit card +required) MongoDB Atlas deployment by following the steps in this guide. + +Follow the steps in this guide to create a sample {+odm+} web application +that connects to a MongoDB deployment. + +.. TODO .. tip:: +.. +.. You can download the complete web application project by cloning the +.. `mongoid-quickstart <>`__ GitHub repository. + +.. toctree:: + + /quick-start-rails/download-and-install/ + /quick-start-rails/create-a-deployment/ + /quick-start-rails/create-a-connection-string/ + /quick-start-rails/configure-mongodb/ + /quick-start-rails/view-data/ + /quick-start-rails/write-data/ + /quick-start-rails/next-steps/ diff --git a/source/quick-start-rails/configure-mongodb.txt b/source/quick-start-rails/configure-mongodb.txt new file mode 100644 index 00000000..e08a2c86 --- /dev/null +++ b/source/quick-start-rails/configure-mongodb.txt @@ -0,0 +1,62 @@ +.. _mongoid-quick-start-rails-connect-to-mongodb: + +================================= +Configure Your MongoDB Connection +================================= + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: test connection, runnable, code example + +.. procedure:: + :style: connected + + .. step:: Configure application for MongoDB + + To configure your application to use MongoDB and {+odm+} as your + ODM, run the following command from the root of your project: + + .. code-block:: bash + + bin/rails g mongoid:config + + After the command completes successfully, your application + contains the ``config/mongoid.yml`` file to configure the + connection to the MongoDB deployment. Your application also + includes the ``config/initializers/mongoid.rb`` file for more + advanced configuration. + + .. step:: Specify target database in connection string + + When connecting to an Atlas cluster, you must specify the database that + you want to interact with as the default database in your connection string. + You must add the database name to your connection string **after the hostname**. + + The following example specifies the ``sample_restaurants`` target database + in a sample connection string: + + .. code-block:: none + + mongodb+srv://user0:pass123@mongo0.example.com/sample_restaurants + + .. step:: Specify connection + + Paste the following configuration into the ``mongoid.yml`` file, + making sure to replace the ```` placeholder + with your connection string that references the target database: + + .. code-block:: yaml + :emphasize-lines: 4 + + development: + clients: + default: + uri: + +After completing these steps, your Rails web application is ready to +connect to MongoDB. + +.. include:: /includes/quick-start/troubleshoot.rst diff --git a/source/quick-start-rails/create-a-connection-string.txt b/source/quick-start-rails/create-a-connection-string.txt new file mode 100644 index 00000000..e9b0a095 --- /dev/null +++ b/source/quick-start-rails/create-a-connection-string.txt @@ -0,0 +1,7 @@ +.. _mongoid-quick-start-rails-create-cxn-str: + +========================== +Create a Connection String +========================== + +.. include:: /includes/quick-start/create-cxn-str.rst \ No newline at end of file diff --git a/source/quick-start-rails/create-a-deployment.txt b/source/quick-start-rails/create-a-deployment.txt new file mode 100644 index 00000000..edfdfed0 --- /dev/null +++ b/source/quick-start-rails/create-a-deployment.txt @@ -0,0 +1,7 @@ +.. _mongoid-quick-start-rails-create-deployment: + +=========================== +Create a MongoDB Deployment +=========================== + +.. include:: /includes/quick-start/create-deployment.rst diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt new file mode 100644 index 00000000..9f483307 --- /dev/null +++ b/source/quick-start-rails/download-and-install.txt @@ -0,0 +1,120 @@ +.. _mongoid-quick-start-rails-download-and-install: + +==================== +Download and Install +==================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: ruby framework, odm, code example + +Prerequisites +------------- + +To create the Quick Start application by using Rails 7, you need the +following software installed in your development environment: + +- `{+language+} `__. + Rails requires {+language+} v3.1.0 or later. Use the latest version + to prevent version conflicts. +- `RubyGems package manager `__. +- A terminal app and shell. For MacOS users, use Terminal or a similar app. + For Windows users, use PowerShell. + +.. tip:: Rails 6 Tutorial + + If you prefer to use Rails 6 to build your application, see the + :ref:`mongoid-getting-started-rails-6` guide. + +Download and Install the {+odm+} and Framework Gems +--------------------------------------------------- + +In {+language+}, packages are called **gems**. + +Complete the following steps to install and add the {+odm+} and Rails +gems to your web application. + +.. procedure:: + :style: connected + + .. step:: Install Rails + + Install the ``rails`` gem, which provides a command-line + interface to create an application skeleton and application + components. + + Run the following command to install ``rails``: + + .. code-block:: bash + + gem install rails + + .. step:: Create a Rails skeleton app + + Run the following commands to create a new Rails application + directory and enter it: + + .. code-block:: bash + + rails new {+quickstart-rails-app-name+} --skip-active-record + cd {+quickstart-rails-app-name+} + + The ``--skip-active-record`` flag instructs Rails to not add + ``ActiveRecord`` as a dependency, as you will use {+odm+} + instead. + + .. tip:: MacOS Installation Issue + + If you are using macOS, you might encounter issues when creating a + new Rails app during the automatic bundle installation step. + First, make sure that your macOS and `Xcode + `__ versions are up to + date. If you receive an error message similar to the following, + you might need to update or configure your build tools: + + .. code-block:: none + :copyable: false + + The compiler failed to generate an executable file. + ... + (RuntimeError) You have to install development tools first. + + Run the following commands to install Xcode command line tools: + + .. code-block:: bash + + xcode-select --install + xcodebuild -license accept + + Then, try to run the ``bundle install`` command again. + + .. step:: Add the {+odm+} gem + + Open the ``Gemfile`` in your application and add the following + entry: + + .. code-block:: ruby + + gem 'mongoid' + + .. step:: Install gems + + Run the following command to install the gems into your + application: + + .. code-block:: bash + + gem install bundler + bundle install + + When the command runs successfully, the output in your + shell contains a ``Bundle complete!`` message and describes the + number of new gems installed. + + After completing these steps, you have a new Rails web application with + {+odm+} installed. + +.. include:: /includes/quick-start/troubleshoot.rst diff --git a/source/quick-start-rails/next-steps.txt b/source/quick-start-rails/next-steps.txt new file mode 100644 index 00000000..9e7bcde4 --- /dev/null +++ b/source/quick-start-rails/next-steps.txt @@ -0,0 +1,32 @@ +.. _mongoid-quick-start-rails-next-steps: + +========== +Next Steps +========== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: learn more + +Congratulations on completing the Quick Start tutorial for Ruby on Rails +7! + +After you complete these steps, you have a Rails web application that +uses {+odm+} to connect to your MongoDB deployment, run a query on +the sample data, and render retrieved results. + +.. TODO You can download the completed web application project by cloning the +.. `mongoid-quickstart <>`__ +.. GitHub repository. + +.. TODO Learn more about {+odm+} features from the following resources: + +.. - :ref:`mongoid-fundamentals-connection`: Learn how to configure your MongoDB +.. connection. +.. +.. - :ref:`mongoid-usage-examples`: See code examples of frequently used MongoDB +.. operations. + diff --git a/source/quick-start-rails/view-data.txt b/source/quick-start-rails/view-data.txt new file mode 100644 index 00000000..ec10b1d9 --- /dev/null +++ b/source/quick-start-rails/view-data.txt @@ -0,0 +1,90 @@ +.. _mongoid-quick-start-rails-view-data: + +================= +View MongoDB Data +================= + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: test connection, runnable, code example + +.. procedure:: + :style: connected + + .. step:: Create a data model + + Run the following command from your project root to create a + ``Restaurant`` model with ``name``, ``cuisine``, and ``borough`` + fields: + + .. code-block:: bash + + bin/rails g scaffold Restaurant name:string cuisine:string borough:string + + This command also creates the controller and view files for the + ``Restaurant`` model. You can find the directories that contain + these files in the ``app`` directory of your application. + + .. step:: Retrieve specific documents + + The ``app/controllers/restaurants_controller.rb`` file contains + definable methods that specify how your app handles different + requests. Replace the ``index()`` method with the following code: + + .. code-block:: ruby + + def index + @restaurants = Restaurant + .where(name: /earth/i) + end + + This controller method retrieves restaurant documents in which the + value of the ``name`` field contains the string ``"earth"``. The + results are rendered at the ``/restaurants`` route by default. + + .. step:: Start your Rails application + + Run the following command from the application root directory + to start your {+language+} web server: + + .. code-block:: bash + + bin/rails s + + After the server starts, it outputs the following message + indicating that the application is running on port ``3000``: + + .. code-block:: none + :copyable: false + + => Booting Puma + => Rails 7.2.1 application starting in development + => Run `bin/rails server --help` for more startup options + Puma starting in single mode... + * Puma version: 6.4.3 (ruby 3.2.5-p208) ("The Eagle of Durango") + * Min threads: 3 + * Max threads: 3 + * Environment: development + * PID: 66973 + * Listening on http://127.0.0.1:3000 + * Listening on http://[::1]:3000 + * Listening on http://127.0.2.2:3000 + * Listening on http://127.0.2.3:3000 + Use Ctrl-C to stop + + .. step:: View the restaurant data + + Open the URL http://127.0.2.2:3000/restaurants in your web browser. + The page shows a list of restaurants and details about each of + them: + + .. figure:: /includes/figures/quickstart-rails-list.png + :alt: The rendered list of restaurants + + Rails provides a default interface that allows you to view, edit, + and delete models. + +.. include:: /includes/quick-start/troubleshoot.rst diff --git a/source/quick-start-rails/write-data.txt b/source/quick-start-rails/write-data.txt new file mode 100644 index 00000000..8ed53139 --- /dev/null +++ b/source/quick-start-rails/write-data.txt @@ -0,0 +1,41 @@ +.. _mongoid-quick-start-rails-write-data: + +===================== +Write Data to MongoDB +===================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: test connection, runnable, code example + +.. procedure:: + :style: connected + + .. step:: Create a new restaurant + + In your browser at http://127.0.2.2:3000/restaurants, you can + scroll to the bottom of the list and click the :guilabel:`New + restaurant` link to navigate to the ``/restaurants/new`` route. On + this page, you can fill out the form to create a new restaurant + model and save it to MongoDB. + + The following sample values satisfy the filter criteria so that + the document will appear in the restaurants list: + + - **Name**: Wild Earth Company + - **Cuisine**: American + - **Borough**: Queens + + Click the :guilabel:`Create Restaurant` button to create the + restaurant model and save it. + + .. step:: View the data + + Refresh http://127.0.2.2:3000/restaurants in your web browser + to view the new restaurant entry that you submitted. The inserted + restaurant appears at the bottom of the list. + +.. include:: /includes/quick-start/troubleshoot.rst diff --git a/source/quick-start-sinatra/download-and-install.txt b/source/quick-start-sinatra/download-and-install.txt index 2aff7826..1cb82cd6 100644 --- a/source/quick-start-sinatra/download-and-install.txt +++ b/source/quick-start-sinatra/download-and-install.txt @@ -1,4 +1,4 @@ -.. _mongoid-qs-download-and-install: +.. _mongoid-quick-start-sinatra-download-and-install: ==================== Download and Install @@ -17,7 +17,7 @@ Prerequisites To create the Quick Start application by using Sinatra, you need the following software installed in your development environment: -- `Ruby `__. +- `{+language+} `__. - `RubyGems package manager `__. - A terminal app and shell. For MacOS users, use Terminal or a similar app. For Windows users, use PowerShell. diff --git a/source/tutorials/getting-started-rails6.txt b/source/tutorials/getting-started-rails6.txt index 84cd46c0..04f38229 100644 --- a/source/tutorials/getting-started-rails6.txt +++ b/source/tutorials/getting-started-rails6.txt @@ -1,10 +1,8 @@ -.. _getting-started-6: +.. _mongoid-getting-started-rails-6: -************************* +========================= Getting Started (Rails 6) -************************* - -.. default-domain:: mongodb +========================= .. contents:: On this page :local: @@ -14,11 +12,11 @@ Getting Started (Rails 6) .. note:: - This tutorial is for Ruby on Rails 6. If this is not the version you're using choose - the appropriate tutorial for your Rails version from the navigation menu. + This tutorial is for Ruby on Rails 6. If this is not the version you're using choose + the appropriate tutorial for your Rails version from the navigation menu. New Application -=============== +--------------- This section shows how to create a new Ruby on Rails application using Mongoid for data access. The application will be similar to the blog application @@ -37,9 +35,8 @@ The complete source code for this application can be found in the guide `_ or other Rails guides. - Install ``rails`` ------------------ +~~~~~~~~~~~~~~~~~ We will use a Rails generator to create the application skeleton. In order to do so, the first step is to install the ``rails`` gem: @@ -50,7 +47,7 @@ In order to do so, the first step is to install the ``rails`` gem: Create New Application ----------------------- +~~~~~~~~~~~~~~~~~~~~~~ Use the ``rails`` command to create the application skeleton, as follows: @@ -85,9 +82,8 @@ and ``--skip-system-test`` options: rails new blog --skip-bundle --skip-active-record --skip-test --skip-system-test cd blog - Create Git Repo ---------------- +~~~~~~~~~~~~~~~ While not required, we recommend creating a Git repository for your application: @@ -99,9 +95,8 @@ While not required, we recommend creating a Git repository for your application: Commit your changes as you are following this tutorial. - Add Mongoid ------------ +~~~~~~~~~~~ 1. Modify the ``Gemfile`` to add a reference to the `mongoid `_ gem: @@ -133,11 +128,10 @@ This generator will create the ``config/mongoid.yml`` configuration file other Mongoid-related configuration). Note that as we are not using ActiveRecord we will not have a ``database.yml`` file. - .. _run-locally: Run MongoDB Locally -------------------- +~~~~~~~~~~~~~~~~~~~ The configuration created in the previous step is suitable when a MongoDB server is running locally. If you do not already have a @@ -164,7 +158,7 @@ like this: .. _use-atlas: Use MongoDB Atlas ------------------ +~~~~~~~~~~~~~~~~~ Instead of downloading, installing and running MongoDB locally, you can create a free MongoDB Atlas account and create a `free MongoDB cluster in Atlas @@ -188,9 +182,8 @@ The uncommented contents of ``config/mongoid.yml`` should look like this: options: server_selection_timeout: 5 - Other Rails Dependencies ------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~ If this is the first Rails application you are creating, you may need to install Node.js on your computer. This can be done via your operating system @@ -207,7 +200,7 @@ Finally, install webpacker: Run Application ---------------- +~~~~~~~~~~~~~~~ You can now start the application server by running: @@ -218,9 +211,8 @@ You can now start the application server by running: Access the application by navigating to `localhost:3000 `_. - Add Posts ---------- +~~~~~~~~~ Using the standard Rails scaffolding, Mongoid can generate the necessary model, controller and view files for our blog so that we can quickly begin @@ -233,12 +225,11 @@ creating blog posts: Navigate to `localhost:3000/posts `_ to create posts and see the posts that have already been created. -.. image:: ../img/rails-new-blog.png +.. figure:: ../img/rails-new-blog.png :alt: Screenshot of the new blog - Add Comments ------------- +~~~~~~~~~~~~ To make our application more interactive, let's add the ability for users to add comments to our posts. @@ -364,15 +355,14 @@ You should now be able to leave comments for the posts: .. image:: ../img/rails-blog-new-comment.png :alt: Screenshot of the blog with a new comment being added - Existing Application -==================== +-------------------- Follow these steps to switch an existing Ruby on Rails application to use Mongoid instead of ActiveRecord. Dependencies ------------- +~~~~~~~~~~~~ Remove or comment out any RDBMS libraries like ``sqlite``, ``pg`` etc. mentioned in ``Gemfile``, and add ``mongoid``: @@ -393,7 +383,7 @@ Install gem dependencies: bundle install Loaded Frameworks ------------------ +~~~~~~~~~~~~~~~~~ Examine ``config/application.rb``. If it is requiring all components of Rails via ``require 'rails/all'``, change it to require individual frameworks: @@ -429,7 +419,7 @@ via ``require 'rails/all'``, change it to require individual frameworks: Mongoid. ActiveRecord Configuration --------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~ Review all configuration files (``config/application.rb``, ``config/environments/{development,production.test}.rb``) and remove or @@ -437,7 +427,7 @@ comment out any references to ``config.active_record`` and ``config.active_storage``. Stop Spring ------------ +~~~~~~~~~~~ If your application is using Spring, which is the default on Rails 6, Spring must be stopped after changing dependencies or configuration. @@ -460,7 +450,7 @@ Spring must be stopped after changing dependencies or configuration. application. Mongoid Configuration ---------------------- +~~~~~~~~~~~~~~~~~~~~~ Generate the default Mongoid configuration: @@ -479,7 +469,7 @@ Review the sections :ref:`Run MongoDB Locally ` and MongoDB, and adjust Mongoid configuration (``config/mongoid.yml``) to match. Adjust Models -------------- +~~~~~~~~~~~~~ If your application already has models, these will need to be changed when migrating from ActiveRecord to Mongoid. @@ -529,7 +519,7 @@ Mongoid does not utilize ActiveRecord migrations, since MongoDB does not require a schema to be defined prior to storing data. Data Migration --------------- +~~~~~~~~~~~~~~ If you already have data in a relational database that you would like to transfer to MongoDB, you will need to perform a data migration. As noted @@ -544,9 +534,8 @@ some resources on migrating from an RDBMS to MongoDB such as the `RDBMS to MongoDB Migration Guide `_ and `Modernization Guide `_. - Rails API ---------- +~~~~~~~~~ The process for creating a Rails API application with Mongoid is the same as when creating a regular application, with the only change being the diff --git a/source/tutorials/getting-started-rails7.txt b/source/tutorials/getting-started-rails7.txt index 87a0c07a..8f4dee56 100644 --- a/source/tutorials/getting-started-rails7.txt +++ b/source/tutorials/getting-started-rails7.txt @@ -14,8 +14,9 @@ Getting Started (Rails 7) .. note:: - This tutorial is for Ruby on Rails 7. If this is not the version you're using choose - the appropriate tutorial for your Rails version from the navigation menu. + This tutorial is for Ruby on Rails 7. If this is not the version + you're using choose the appropriate tutorial for your Rails version + from the navigation menu. New Application =============== @@ -32,10 +33,10 @@ The complete source code for this application can be found in the .. note:: - This guide assumes basic familiarity with Ruby on Rails. - To learn more about Ruby on Rails, please refer to its `Getting Started - guide `_ or - other Rails guides. + This guide assumes basic familiarity with Ruby on Rails. + To learn more about Ruby on Rails, please refer to its `Getting Started + guide `_ or + other Rails guides. Install ``rails`` From cbf3705504a3baec6ea5a3c1dd3d645cb5ea7feb Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 14 Oct 2024 15:16:39 -0400 Subject: [PATCH 021/113] vale --- source/quick-start-rails/download-and-install.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index 9f483307..b06e3946 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -73,7 +73,7 @@ gems to your web application. First, make sure that your macOS and `Xcode `__ versions are up to date. If you receive an error message similar to the following, - you might need to update or configure your build tools: + you must update or configure your build tools: .. code-block:: none :copyable: false From e075906980c3a0db06609df687bd2cc3b5acaf9e Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 14 Oct 2024 15:17:33 -0400 Subject: [PATCH 022/113] ordering --- snooty.toml | 1 + source/index.txt | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/snooty.toml b/snooty.toml index ba1c23ea..17839cec 100644 --- a/snooty.toml +++ b/snooty.toml @@ -6,6 +6,7 @@ intersphinx = [ "https://www.mongodb.com/docs/manual/objects.inv", ] toc_landing_pages = [ + "/quick-start-rails", "/quick-start-sinatra" ] diff --git a/source/index.txt b/source/index.txt index 5f792759..31f5e279 100644 --- a/source/index.txt +++ b/source/index.txt @@ -10,16 +10,16 @@ MongoDB in Ruby. To work with {+odm+} from the command line using `_ utility. .. toctree:: - :titlesonly: - - /quick-start-sinatra - /quick-start-rails - installation-configuration - tutorials - schema-configuration - working-with-data - API - release-notes - contributing - additional-resources - ecosystem + :titlesonly: + + /quick-start-rails + /quick-start-sinatra + installation-configuration + tutorials + schema-configuration + working-with-data + API + release-notes + contributing + additional-resources + ecosystem From 4dc3f41545e45dc46d568105e9f249a6df0aaccf Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 14 Oct 2024 15:18:20 -0400 Subject: [PATCH 023/113] small fix --- source/quick-start-rails/download-and-install.txt | 4 ++-- source/quick-start-sinatra/download-and-install.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index b06e3946..bea380dd 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -17,10 +17,10 @@ Prerequisites To create the Quick Start application by using Rails 7, you need the following software installed in your development environment: -- `{+language+} `__. +- `{+language+}. `__ Rails requires {+language+} v3.1.0 or later. Use the latest version to prevent version conflicts. -- `RubyGems package manager `__. +- `RubyGems package manager. `__ - A terminal app and shell. For MacOS users, use Terminal or a similar app. For Windows users, use PowerShell. diff --git a/source/quick-start-sinatra/download-and-install.txt b/source/quick-start-sinatra/download-and-install.txt index 1cb82cd6..b0a92bbf 100644 --- a/source/quick-start-sinatra/download-and-install.txt +++ b/source/quick-start-sinatra/download-and-install.txt @@ -17,8 +17,8 @@ Prerequisites To create the Quick Start application by using Sinatra, you need the following software installed in your development environment: -- `{+language+} `__. -- `RubyGems package manager `__. +- `{+language+}. `__ +- `RubyGems package manager. `__ - A terminal app and shell. For MacOS users, use Terminal or a similar app. For Windows users, use PowerShell. From 1ae3f0eb9f9fc25bb2e3c04c36e4ec47c6dd39b3 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 15 Oct 2024 10:50:48 -0400 Subject: [PATCH 024/113] MW PR fixes 1 --- source/quick-start-rails.txt | 25 ++++++++++--------- .../quick-start-rails/configure-mongodb.txt | 6 ++--- .../download-and-install.txt | 9 ++++--- source/quick-start-rails/view-data.txt | 9 ++++--- source/quick-start-sinatra.txt | 10 ++++++-- .../quick-start-sinatra/configure-mongodb.txt | 4 +-- source/quick-start-sinatra/view-data.txt | 2 +- source/tutorials/getting-started-rails6.txt | 16 +++++++----- source/tutorials/getting-started-rails7.txt | 2 +- 9 files changed, 48 insertions(+), 35 deletions(-) diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index 09224647..e88c56d1 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -20,9 +20,10 @@ Quick Start (Ruby on Rails) Overview -------- -This guide shows you how to use {+odm+} in a new **Ruby on Rails 7** web -application, connect to a MongoDB cluster hosted on MongoDB Atlas, and -perform read and write operations on the data in your cluster. +This guide shows you how to use {+odm+} in a new **Ruby on Rails 7 (Rails)** +web application, connect to a MongoDB cluster hosted on MongoDB +Atlas, and perform read and write operations on the data in your +cluster. .. tip:: @@ -37,13 +38,20 @@ create flexible data models. Ruby on Rails, or Rails, is a web application framework for {+language+}. Rails applications use a model-view-controller (MVC) architecture that allows you to easily control how your data is -modeled and displayed. {+odm+} replaces Rails' default -``ActiveRecord`` adapter for data modeling. +modeled and displayed. {+odm+} replaces the default ``ActiveRecord`` +adapter for data modeling in Rails. To learn more about Ruby on Rails, see the `Getting Started with Rails `__ guide in the Rails documentation. +MongoDB Atlas is a fully managed cloud database service that hosts your +MongoDB deployments. You can create your own free (no credit card +required) MongoDB Atlas deployment by following the steps in this guide. + +Follow the steps in this guide to create a sample {+odm+} web application +that connects to a MongoDB deployment. + .. tip:: Other Framework Tutorials If you prefer to use Rails 6 to build your application, see the @@ -52,13 +60,6 @@ guide in the Rails documentation. If you prefer to use Sinatra as your web framework, see the :ref:`mongoid-quick-start-sinatra` guide. -MongoDB Atlas is a fully managed cloud database service that hosts your -MongoDB deployments. You can create your own free (no credit card -required) MongoDB Atlas deployment by following the steps in this guide. - -Follow the steps in this guide to create a sample {+odm+} web application -that connects to a MongoDB deployment. - .. TODO .. tip:: .. .. You can download the complete web application project by cloning the diff --git a/source/quick-start-rails/configure-mongodb.txt b/source/quick-start-rails/configure-mongodb.txt index e08a2c86..7847e0c5 100644 --- a/source/quick-start-rails/configure-mongodb.txt +++ b/source/quick-start-rails/configure-mongodb.txt @@ -33,7 +33,7 @@ Configure Your MongoDB Connection When connecting to an Atlas cluster, you must specify the database that you want to interact with as the default database in your connection string. - You must add the database name to your connection string **after the hostname**. + You must add the database name to your connection string *after the hostname*. The following example specifies the ``sample_restaurants`` target database in a sample connection string: @@ -42,9 +42,9 @@ Configure Your MongoDB Connection mongodb+srv://user0:pass123@mongo0.example.com/sample_restaurants - .. step:: Specify connection + .. step:: Specify connection in mongoid.yml - Paste the following configuration into the ``mongoid.yml`` file, + Paste the following configuration into the ``config/mongoid.yml`` file, making sure to replace the ```` placeholder with your connection string that references the target database: diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index bea380dd..637f53c9 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -43,7 +43,7 @@ gems to your web application. .. step:: Install Rails Install the ``rails`` gem, which provides a command-line - interface to create an application skeleton and application + interface to create a basic application structure and application components. Run the following command to install ``rails``: @@ -52,10 +52,10 @@ gems to your web application. gem install rails - .. step:: Create a Rails skeleton app + .. step:: Create a Rails app with default scaffolding Run the following commands to create a new Rails application - directory and enter it: + directory with default scaffolding and enter the application: .. code-block:: bash @@ -63,7 +63,8 @@ gems to your web application. cd {+quickstart-rails-app-name+} The ``--skip-active-record`` flag instructs Rails to not add - ``ActiveRecord`` as a dependency, as you will use {+odm+} + ``ActiveRecord`` as a dependency. You don't need this + dependency because you will use {+odm+} instead. .. tip:: MacOS Installation Issue diff --git a/source/quick-start-rails/view-data.txt b/source/quick-start-rails/view-data.txt index ec10b1d9..57ad2ef2 100644 --- a/source/quick-start-rails/view-data.txt +++ b/source/quick-start-rails/view-data.txt @@ -31,8 +31,8 @@ View MongoDB Data .. step:: Retrieve specific documents The ``app/controllers/restaurants_controller.rb`` file contains - definable methods that specify how your app handles different - requests. Replace the ``index()`` method with the following code: + methods that specify how your app handles different + requests. Replace the ``index()`` method body with the following code: .. code-block:: ruby @@ -41,7 +41,7 @@ View MongoDB Data .where(name: /earth/i) end - This controller method retrieves restaurant documents in which the + This controller method retrieves ``Restaurant`` documents in which the value of the ``name`` field contains the string ``"earth"``. The results are rendered at the ``/restaurants`` route by default. @@ -85,6 +85,7 @@ View MongoDB Data :alt: The rendered list of restaurants Rails provides a default interface that allows you to view, edit, - and delete models. + and delete models. In the next section, you can learn how to use the + interface to interact with MongoDB data. .. include:: /includes/quick-start/troubleshoot.rst diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index 5b9e69c4..9631447e 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -38,8 +38,6 @@ Sinatra is a domain-specific language (DSL) for creating web applications in {+language+}. Sinatra applications are simple to set up and can provide faster request processing than other frameworks. -.. TODO .. tip:: If you prefer to use Rails as your web framework, see the Quick Start (Rails) guide. - MongoDB Atlas is a fully managed cloud database service that hosts your MongoDB deployments. You can create your own free (no credit card required) MongoDB Atlas deployment by following the steps in this guide. @@ -47,6 +45,14 @@ required) MongoDB Atlas deployment by following the steps in this guide. Follow the steps in this guide to create a sample {+odm+} web application that connects to a MongoDB deployment. +.. tip:: Other Framework Tutorials + + If you prefer to use Ruby on Rails 6 to build your application, see the + :ref:`mongoid-getting-started-rails-6` guide. + + If you prefer to use Ruby on Rails 7 as your web framework, see the + :ref:`mongoid-quick-start-rails` guide. + .. TODO .. tip:: .. .. You can download the complete web application project by cloning the diff --git a/source/quick-start-sinatra/configure-mongodb.txt b/source/quick-start-sinatra/configure-mongodb.txt index 716104d1..8f79908c 100644 --- a/source/quick-start-sinatra/configure-mongodb.txt +++ b/source/quick-start-sinatra/configure-mongodb.txt @@ -18,7 +18,7 @@ Configure Your MongoDB Connection When connecting to an Atlas cluster, you must specify the database that you want to interact with as the default database in your connection string. - You must add the database name to your connection string **after the hostname**. + You must add the database name to your connection string *after the hostname*. The following example specifies the ``sample_restaurants`` target database in a sample connection string: @@ -27,7 +27,7 @@ Configure Your MongoDB Connection mongodb+srv://user0:pass123@mongo0.example.com/sample_restaurants - .. step:: Specify connection + .. step:: Specify connection in mongoid.yml At the root level of your project, create a ``config`` directory. Then, create a file in this directory called ``mongoid.yml``. diff --git a/source/quick-start-sinatra/view-data.txt b/source/quick-start-sinatra/view-data.txt index 8498eb4f..00171ffc 100644 --- a/source/quick-start-sinatra/view-data.txt +++ b/source/quick-start-sinatra/view-data.txt @@ -94,7 +94,7 @@ View MongoDB Data erb :list_restaurants end - This route retrieves restaurant documents in which the value of + This route retrieves ``Restaurant`` documents in which the value of the ``name`` field contains the string ``"earth"``. The route uses the ``list_restaurants`` view to render the results. diff --git a/source/tutorials/getting-started-rails6.txt b/source/tutorials/getting-started-rails6.txt index 04f38229..719c73ba 100644 --- a/source/tutorials/getting-started-rails6.txt +++ b/source/tutorials/getting-started-rails6.txt @@ -10,10 +10,14 @@ Getting Started (Rails 6) :depth: 2 :class: singlecol +In this guide, you can learn how to implement {+odm} in a Ruby on Rails +6 web application. View the following sections to learn how to integrate +{+odm+} in new applications or how to add it to existing applications. + .. note:: - This tutorial is for Ruby on Rails 6. If this is not the version you're using choose - the appropriate tutorial for your Rails version from the navigation menu. + This tutorial is for Ruby on Rails 6. To use Ruby on Rails 7 as your + web framework, see the :ref:`mongoid-quick-start-rails` guide. New Application --------------- @@ -21,18 +25,18 @@ New Application This section shows how to create a new Ruby on Rails application using Mongoid for data access. The application will be similar to the blog application described in the `Ruby on Rails Getting Started -`_ +`__ guide, however using Mongoid instead of ActiveRecord as the database adapter. The complete source code for this application can be found in the -`mongoid-demo GitHub repository -`_. +`mongoid-demo GitHub repository. +`__ .. note:: This guide assumes basic familiarity with Ruby on Rails. To learn more about Ruby on Rails, please refer to its `Getting Started - guide `_ or + guide `__ or other Rails guides. Install ``rails`` diff --git a/source/tutorials/getting-started-rails7.txt b/source/tutorials/getting-started-rails7.txt index 8f4dee56..b413e159 100644 --- a/source/tutorials/getting-started-rails7.txt +++ b/source/tutorials/getting-started-rails7.txt @@ -15,7 +15,7 @@ Getting Started (Rails 7) .. note:: This tutorial is for Ruby on Rails 7. If this is not the version - you're using choose the appropriate tutorial for your Rails version + you're using, choose the appropriate tutorial for your Rails version from the navigation menu. New Application From 22781f486147cb757974e03a7ada956d97993708 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 15 Oct 2024 10:54:00 -0400 Subject: [PATCH 025/113] small fixes --- source/quick-start-rails/write-data.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/quick-start-rails/write-data.txt b/source/quick-start-rails/write-data.txt index 8ed53139..601c9b0e 100644 --- a/source/quick-start-rails/write-data.txt +++ b/source/quick-start-rails/write-data.txt @@ -19,11 +19,11 @@ Write Data to MongoDB In your browser at http://127.0.2.2:3000/restaurants, you can scroll to the bottom of the list and click the :guilabel:`New restaurant` link to navigate to the ``/restaurants/new`` route. On - this page, you can fill out the form to create a new restaurant + this page, you can fill out the form to create a new ``Restaurant`` model and save it to MongoDB. The following sample values satisfy the filter criteria so that - the document will appear in the restaurants list: + the document will appear in the list of restaurants: - **Name**: Wild Earth Company - **Cuisine**: American @@ -35,7 +35,7 @@ Write Data to MongoDB .. step:: View the data Refresh http://127.0.2.2:3000/restaurants in your web browser - to view the new restaurant entry that you submitted. The inserted - restaurant appears at the bottom of the list. + to view the new ``Restaurant`` entry that you submitted at the + bottom of the list. .. include:: /includes/quick-start/troubleshoot.rst From 7ee7a35d3bae771d815f99ae8d650ef14e9e90e0 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 16 Oct 2024 09:59:17 -0400 Subject: [PATCH 026/113] MW PR fixes 2 --- source/quick-start-rails.txt | 2 +- source/quick-start-rails/download-and-install.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index e88c56d1..c69b8418 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -35,7 +35,7 @@ cluster. Ruby. By using {+odm+}, you can easily interact with your data and create flexible data models. -Ruby on Rails, or Rails, is a web application framework for +Ruby on Rails is a web application framework for {+language+}. Rails applications use a model-view-controller (MVC) architecture that allows you to easily control how your data is modeled and displayed. {+odm+} replaces the default ``ActiveRecord`` diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index 637f53c9..88fa652d 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -14,7 +14,7 @@ Download and Install Prerequisites ------------- -To create the Quick Start application by using Rails 7, you need the +To create the Quick Start application by using Ruby on Rails 7, you need the following software installed in your development environment: - `{+language+}. `__ From a25eede1b8a6f76a0d200060d951cfcbc1166bfb Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 23 Oct 2024 11:24:20 -0400 Subject: [PATCH 027/113] DOCSP-44647: add to existing app --- source/add-existing.txt | 232 ++++++++++++++++++ source/index.txt | 17 +- source/quick-start-rails.txt | 7 +- .../download-and-install.txt | 2 +- source/quick-start-sinatra.txt | 5 +- source/reference/compatibility.txt | 2 +- source/reference/crud.txt | 2 +- source/reference/fields.txt | 3 +- source/tutorials/getting-started-rails6.txt | 2 +- source/tutorials/getting-started-rails7.txt | 2 +- 10 files changed, 256 insertions(+), 18 deletions(-) create mode 100644 source/add-existing.txt diff --git a/source/add-existing.txt b/source/add-existing.txt new file mode 100644 index 00000000..fe760f51 --- /dev/null +++ b/source/add-existing.txt @@ -0,0 +1,232 @@ +.. _mongoid-add-to-existing: + +====================================== +Add {+odm+} to an Existing Application +====================================== + +.. facet:: + :name: genre + :values: tutorial + +.. meta:: + :keywords: ruby framework, odm, migrate + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to add {+odm+} to an existing Sinatra +or Ruby on Rails (Rails) application. To learn how to set up a new +application that uses {+odm+}, see one of the following guides: + +- :ref:`mongoid-quick-start-rails` +- :ref:`mongoid-quick-start-sinatra` + +Sinatra Application +------------------- + +To start using {+odm+} in an existing Sinatra application, you can follow +the same steps described in the +:ref:`Sinatra Quick Start ` guide. + +The following steps describe how to add {+odm+} to a Sinatra application: + +1. Add the ``mongoid`` dependency to your application's ``Gemfile``. + +#. Create a ``config/mongoid.yml`` configuration file and specify your + connection target. + +#. Create an application file and load your configuration file. + +#. Create {+odm+} models. + +Rails Application +----------------- + +You can add {+odm+} to an existing Rails application to run alongside +other ActiveRecord adapters. To use a combination of adapters, you +can add the ``mongoid`` dependency and populate the configuration file +with your connection information to start using MongoDB in your +application. + +To adapt an existing Rails application to use only {+odm+} instead of +ActiveRecord, you must make additional configuration changes, as +described in the following sections. + +Modify Dependencies +~~~~~~~~~~~~~~~~~~~ + +Add the ``mongoid`` gem to your application's ``Gemfile``: + +.. code-block:: ruby + :caption: Gemfile + + gem 'mongoid' + +To use {+odm+} as the *only* database adapter, remove or comment out any +RDBMS libraries such as ``sqlite`` or ``pg`` listed in the ``Gemfile``. + +Then, install the dependencies by running the following command: + +.. code-block:: sh + + bundle install + +{+odm+} Configuration +~~~~~~~~~~~~~~~~~~~~~ + +Generate the default {+odm+} configuration by running the following +command: + +.. code-block:: sh + + bin/rails g mongoid:config + +This generator creates the ``config/mongoid.yml`` configuration file +used to configure the connection to the MongoDB deployment and the +``config/initializers/mongoid.rb`` initializer file that you can use +to set other options. + +In the ``config/mongoid.yml`` file, specify your connection string and +other connection options. + +Modify Frameworks +~~~~~~~~~~~~~~~~~ + +Open the ``config/application.rb`` file and examine the contents. If it +requires all of the components of Rails by including the ``require +'rails/all'`` specification, change this to specify individual +frameworks. + +To verify the contents of ``rails/all`` for Rails 7, see the +:github:`rails GitHub repository +`. + +The following code is a sample ``config/application.rb`` file that +demonstrates how to specify individual frameworks instead of using ``rails/all``: + +.. code-block:: ruby + :caption: config/application.rb + + # Remove or comment out rails/all + #require "rails/all" + + # Add the following instead of rails/all: + require "rails" + + # Comment out unneeded frameworks + # require "active_record/railtie" rescue LoadError + # require "active_storage/engine" rescue LoadError + require "action_controller/railtie" rescue LoadError + require "action_view/railtie" rescue LoadError + require "action_mailer/railtie" rescue LoadError + require "active_job/railtie" rescue LoadError + require "action_cable/engine" rescue LoadError + # require "action_mailbox/engine" rescue LoadError + # require "action_text/engine" rescue LoadError + require "rails/test_unit/railtie" rescue LoadError + +.. note:: + + Because they rely on ActiveRecord, the `ActionText + `__, + `ActiveStorage `__ and + `ActionMailbox + `__ + adapters cannot be used alongside {+odm+}. + +Disable ActiveRecord +~~~~~~~~~~~~~~~~~~~~ + +Review your application's configuration files, such as +``config/application.rb``, then remove or comment out any references to +``config.active_record`` and ``config.active_storage``. + +Adjust Models +~~~~~~~~~~~~~ + +If your application already uses models, you need to adjust them to +migrate from using ActiveRecord to {+odm+}. + +ActiveRecord models derive from ``ApplicationRecord`` and do not have +column definitions. {+odm+} models generally have no superclass but must +include the ``Mongoid::Document`` attribute. {+odm+} models usually +define fields explicitly. You can also use :ref:`dynamic fields +` instead of explicit field definitions. + +For example, a basic ActiveRecord ``Post`` model might resemble the +following: + +.. code-block:: ruby + :caption: app/models/post.rb + + class Post < ApplicationRecord + has_many :comments, dependent: :destroy + end + +A similar {+odm+} ``Post`` model might resemble the following: + +.. code-block:: ruby + :caption: app/models/post.rb + + class Post + include Mongoid::Document + + field :title, type: String + field :body, type: String + + has_many :comments, dependent: :destroy + end + +Or, you can define the ``Post`` model by using dynamic fields, as shown +in the following code: + +.. code-block:: ruby + :caption: app/models/post.rb + + class Post + include Mongoid::Document + include Mongoid::Attributes::Dynamic + + has_many :comments, dependent: :destroy + end + +{+odm+} does not use ActiveRecord migrations, because MongoDB does not +require a defined schema before you can store data. + +Data Migration +~~~~~~~~~~~~~~ + +If you already have data in a relational database that you want to +move into MongoDB, you must perform a data migration. You do not need to +perform a schema migration because MongoDB does not require +a predefined schema to store the data. + +Migration tools are often specific to datasets. +Even though {+odm+} supports a superset of ActiveRecord associations, +the way that model references are stored in collections is different between +{+odm+} and ActiveRecord. + +Visit the following resources to learn more about migrating from an +RDBMS to MongoDB: + +- `RDBMS to MongoDB Migration Guide + `__ + in the AWS documentation + +- :website:`Modernize your apps with MongoDB Atlas + ` on the MongoDB website + +Rails API +~~~~~~~~~ + +The process for creating a Rails API application that uses {+odm+} is +almost the same as for creating a normal application. The only +difference is that you must add the ``--api`` flag when running ``rails +new`` to create the application. Migrating a Rails API application to +{+odm+} follows the same process described in the preceding sections. diff --git a/source/index.txt b/source/index.txt index 31f5e279..10d08125 100644 --- a/source/index.txt +++ b/source/index.txt @@ -14,12 +14,13 @@ MongoDB in Ruby. To work with {+odm+} from the command line using /quick-start-rails /quick-start-sinatra - installation-configuration - tutorials - schema-configuration - working-with-data + /add-existing + /installation-configuration + /tutorials + /schema-configuration + /working-with-data API - release-notes - contributing - additional-resources - ecosystem + /release-notes + /contributing + /additional-resources + /ecosystem diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index c69b8418..b05d1e18 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -1,7 +1,7 @@ .. _mongoid-quick-start-rails: =========================== -Quick Start (Ruby on Rails) +Quick Start - Ruby on Rails =========================== .. facet:: @@ -25,6 +25,9 @@ web application, connect to a MongoDB cluster hosted on MongoDB Atlas, and perform read and write operations on the data in your cluster. +To learn how to migrate an existing application to use {+odm+}, see the +:ref:`mongoid-add-to-existing` guide. + .. tip:: If you prefer to connect to MongoDB by using the {+ruby-driver+} without @@ -38,7 +41,7 @@ create flexible data models. Ruby on Rails is a web application framework for {+language+}. Rails applications use a model-view-controller (MVC) architecture that allows you to easily control how your data is -modeled and displayed. {+odm+} replaces the default ``ActiveRecord`` +modeled and displayed. {+odm+} replaces the default ActiveRecord adapter for data modeling in Rails. To learn more about Ruby on Rails, see the `Getting Started diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index 88fa652d..6bd7f25f 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -63,7 +63,7 @@ gems to your web application. cd {+quickstart-rails-app-name+} The ``--skip-active-record`` flag instructs Rails to not add - ``ActiveRecord`` as a dependency. You don't need this + ActiveRecord as a dependency. You don't need this dependency because you will use {+odm+} instead. diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index 9631447e..b77aefaf 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -1,7 +1,7 @@ .. _mongoid-quick-start-sinatra: ===================== -Quick Start (Sinatra) +Quick Start - Sinatra ===================== .. facet:: @@ -24,6 +24,9 @@ This guide shows you how to use {+odm+} in a new Sinatra web application, connect to a MongoDB cluster hosted on MongoDB Atlas, and perform read and write operations on the data in your cluster. +To learn how to migrate an existing application to use {+odm+}, see the +:ref:`mongoid-add-to-existing` guide. + .. tip:: If you prefer to connect to MongoDB by using the {+ruby-driver+} without diff --git a/source/reference/compatibility.txt b/source/reference/compatibility.txt index 659fae45..7dd2ae19 100644 --- a/source/reference/compatibility.txt +++ b/source/reference/compatibility.txt @@ -481,7 +481,7 @@ Mongoid is used as a drop-in replacement. ``ActionCable``, however any existing adapter (such as `Redis `_) can be used successfully in conjunction with Mongoid models -.. [#rails-activerecord-dependency] Depends directly on ``ActiveRecord`` +.. [#rails-activerecord-dependency] Depends directly on ActiveRecord .. [#rails-activemodel-dependency] ``Mongoid::Document`` includes ``ActiveModel::Model`` and leverages ``ActiveModel::Validations`` for validations diff --git a/source/reference/crud.txt b/source/reference/crud.txt index ff70fa6f..33d2b077 100644 --- a/source/reference/crud.txt +++ b/source/reference/crud.txt @@ -707,7 +707,7 @@ each declared field: # => "Artem" To use this mechanism, each field must be explicitly declared, or the -model class must enable :ref:`dynamic fields `. +model class must enable :ref:`dynamic fields `. Custom Getters & Setters diff --git a/source/reference/fields.txt b/source/reference/fields.txt index 5e29bfee..e53ce131 100644 --- a/source/reference/fields.txt +++ b/source/reference/fields.txt @@ -1251,8 +1251,7 @@ Then, use it your model class: Note that the handler function will be invoked whenever the option is used in the field definition, even if the option's value is false or nil. - -.. _dynamic-fields: +.. _mongoid-dynamic-fields: Dynamic Fields ============== diff --git a/source/tutorials/getting-started-rails6.txt b/source/tutorials/getting-started-rails6.txt index 719c73ba..0b5e32bd 100644 --- a/source/tutorials/getting-started-rails6.txt +++ b/source/tutorials/getting-started-rails6.txt @@ -481,7 +481,7 @@ migrating from ActiveRecord to Mongoid. ActiveRecord models derive from ``ApplicationRecord`` and do not have column definitions. Mongoid models generally have no superclass but must include ``Mongoid::Document``, and usually define the fields explicitly -(but :ref:`dynamic fields ` may also be used instead of +(but :ref:`dynamic fields ` may also be used instead of explicit field definitions). For example, a bare-bones Post model may look like this in ActiveRecord: diff --git a/source/tutorials/getting-started-rails7.txt b/source/tutorials/getting-started-rails7.txt index b413e159..9e7c282d 100644 --- a/source/tutorials/getting-started-rails7.txt +++ b/source/tutorials/getting-started-rails7.txt @@ -400,7 +400,7 @@ migrating from ActiveRecord to Mongoid. ActiveRecord models derive from ``ApplicationRecord`` and do not have column definitions. Mongoid models generally have no superclass but must include ``Mongoid::Document``, and usually define the fields explicitly -(but :ref:`dynamic fields ` may also be used instead of +(but :ref:`dynamic fields ` may also be used instead of explicit field definitions). For example, a bare-bones Post model may look like this in ActiveRecord: From 78083c92d2db9c3df1bd6a3bf00bb2d78b0598cb Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 23 Oct 2024 11:25:52 -0400 Subject: [PATCH 028/113] fix vale action --- .github/workflows/vale-tdbx.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/vale-tdbx.yml b/.github/workflows/vale-tdbx.yml index e33882b5..84de594a 100644 --- a/.github/workflows/vale-tdbx.yml +++ b/.github/workflows/vale-tdbx.yml @@ -12,27 +12,30 @@ jobs: - name: checkout uses: actions/checkout@v4 + - name: Install docutils + run: sudo apt-get install -y docutils + - id: files uses: masesgroup/retrieve-changed-files@v2 with: - format: 'csv' + format: "csv" - name: checkout-latest-rules uses: actions/checkout@v4 with: repository: mongodb/mongodb-vale-action - path: './tdbx-vale-rules' + path: "./tdbx-vale-rules" token: ${{secrets.GITHUB_TOKEN}} - name: move-files-for-vale-action run: | - cp tdbx-vale-rules/.vale.ini .vale.ini - mkdir -p .github/styles/ - cp -rf tdbx-vale-rules/.github/styles/ .github/ + cp tdbx-vale-rules/.vale.ini .vale.ini + mkdir -p .github/styles/ + cp -rf tdbx-vale-rules/.github/styles/ .github/ - name: run-vale uses: errata-ai/vale-action@reviewdog with: reporter: github-pr-check files: ${{steps.files.outputs.added_modified}} fail_on_error: true - token: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file + token: ${{secrets.GITHUB_TOKEN}} From 8310a076681987c33c112989873d342809990bc6 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 23 Oct 2024 11:29:58 -0400 Subject: [PATCH 029/113] vale fixes --- source/add-existing.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/add-existing.txt b/source/add-existing.txt index fe760f51..3f8edb5c 100644 --- a/source/add-existing.txt +++ b/source/add-existing.txt @@ -55,7 +55,7 @@ with your connection information to start using MongoDB in your application. To adapt an existing Rails application to use only {+odm+} instead of -ActiveRecord, you must make additional configuration changes, as +ActiveRecord, you must make other configuration changes, as described in the following sections. Modify Dependencies @@ -99,7 +99,7 @@ Modify Frameworks ~~~~~~~~~~~~~~~~~ Open the ``config/application.rb`` file and examine the contents. If it -requires all of the components of Rails by including the ``require +requires all the components of Rails by including the ``require 'rails/all'`` specification, change this to specify individual frameworks. @@ -150,7 +150,7 @@ Review your application's configuration files, such as Adjust Models ~~~~~~~~~~~~~ -If your application already uses models, you need to adjust them to +If your application already uses models, you must adjust them to migrate from using ActiveRecord to {+odm+}. ActiveRecord models derive from ``ApplicationRecord`` and do not have @@ -203,8 +203,8 @@ Data Migration ~~~~~~~~~~~~~~ If you already have data in a relational database that you want to -move into MongoDB, you must perform a data migration. You do not need to -perform a schema migration because MongoDB does not require +move into MongoDB, you must perform a data migration. You don't have to +perform schema migration because MongoDB does not require a predefined schema to store the data. Migration tools are often specific to datasets. From c3592bebfea4157838a8fbb02ef5d4ab9bba3e53 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 23 Oct 2024 13:44:24 -0400 Subject: [PATCH 030/113] depth --- source/add-existing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/add-existing.txt b/source/add-existing.txt index 3f8edb5c..a4385986 100644 --- a/source/add-existing.txt +++ b/source/add-existing.txt @@ -14,7 +14,7 @@ Add {+odm+} to an Existing Application .. contents:: On this page :local: :backlinks: none - :depth: 1 + :depth: 2 :class: singlecol Overview From 85f7765ed3614306ecb301b19cce109535f21147 Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 24 Oct 2024 10:49:38 -0400 Subject: [PATCH 031/113] MW PR fixes 1 --- source/add-existing.txt | 73 +++++++++---------- source/quick-start-rails.txt | 4 +- .../download-and-install.txt | 5 +- source/quick-start-sinatra.txt | 2 +- 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/source/add-existing.txt b/source/add-existing.txt index a4385986..f8f257b2 100644 --- a/source/add-existing.txt +++ b/source/add-existing.txt @@ -31,8 +31,7 @@ Sinatra Application ------------------- To start using {+odm+} in an existing Sinatra application, you can follow -the same steps described in the -:ref:`Sinatra Quick Start ` guide. +the steps described in the :ref:`mongoid-quick-start-sinatra` guide. The following steps describe how to add {+odm+} to a Sinatra application: @@ -41,7 +40,9 @@ The following steps describe how to add {+odm+} to a Sinatra application: #. Create a ``config/mongoid.yml`` configuration file and specify your connection target. -#. Create an application file and load your configuration file. +#. Create an application file and load your configuration file, as shown + in the :ref:`mongoid-quick-start-sinatra-view-data` step of the Quick + Start guide. #. Create {+odm+} models. @@ -49,13 +50,13 @@ Rails Application ----------------- You can add {+odm+} to an existing Rails application to run alongside -other ActiveRecord adapters. To use a combination of adapters, you +other Active Record adapters. To use a combination of adapters, you can add the ``mongoid`` dependency and populate the configuration file with your connection information to start using MongoDB in your application. To adapt an existing Rails application to use only {+odm+} instead of -ActiveRecord, you must make other configuration changes, as +Active Record, you must make other configuration changes, as described in the following sections. Modify Dependencies @@ -69,7 +70,8 @@ Add the ``mongoid`` gem to your application's ``Gemfile``: gem 'mongoid' To use {+odm+} as the *only* database adapter, remove or comment out any -RDBMS libraries such as ``sqlite`` or ``pg`` listed in the ``Gemfile``. +RDBMS libraries listed in the ``Gemfile``, such as ``sqlite`` or +``pg``. Then, install the dependencies by running the following command: @@ -98,17 +100,11 @@ other connection options. Modify Frameworks ~~~~~~~~~~~~~~~~~ -Open the ``config/application.rb`` file and examine the contents. If it -requires all the components of Rails by including the ``require -'rails/all'`` specification, change this to specify individual -frameworks. - -To verify the contents of ``rails/all`` for Rails 7, see the -:github:`rails GitHub repository -`. - -The following code is a sample ``config/application.rb`` file that -demonstrates how to specify individual frameworks instead of using ``rails/all``: +Open the ``config/application.rb`` file and examine the contents. If the +file uses the ``require "rails/all"`` statement to load all Rails components, +delete this statement. You must add a separate ``require`` statement +for each Rails component, as shown the following sample +``config/application.rb`` file: .. code-block:: ruby :caption: config/application.rb @@ -133,33 +129,37 @@ demonstrates how to specify individual frameworks instead of using ``rails/all`` .. note:: - Because they rely on ActiveRecord, the `ActionText + Because they rely on Active Record, the `ActionText `__, - `ActiveStorage `__ and + `ActiveStorage `__, and `ActionMailbox `__ adapters cannot be used alongside {+odm+}. -Disable ActiveRecord -~~~~~~~~~~~~~~~~~~~~ +Disable Active Record Adapters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Review your application's configuration files, such as -``config/application.rb``, then remove or comment out any references to +In ``config/application.rb`` and your application's other configuration +files, remove or comment out any references to ``config.active_record`` and ``config.active_storage``. Adjust Models ~~~~~~~~~~~~~ -If your application already uses models, you must adjust them to -migrate from using ActiveRecord to {+odm+}. +To migrate from using Active Record to {+odm+}, you must adjust your +application's existing models. + +Active Record models derive from the ``ApplicationRecord`` class and do +not have column definitions, while {+odm+} models generally have no +superclass but must include the ``Mongoid::Document`` attribute. -ActiveRecord models derive from ``ApplicationRecord`` and do not have -column definitions. {+odm+} models generally have no superclass but must -include the ``Mongoid::Document`` attribute. {+odm+} models usually -define fields explicitly. You can also use :ref:`dynamic fields -` instead of explicit field definitions. +When creating {+odm+} models, you can define fields in the following +ways: -For example, a basic ActiveRecord ``Post`` model might resemble the +- Define fields explicitly +- Use :ref:`dynamic fields ` + +For example, a basic Active Record ``Post`` model might resemble the following: .. code-block:: ruby @@ -183,8 +183,8 @@ A similar {+odm+} ``Post`` model might resemble the following: has_many :comments, dependent: :destroy end -Or, you can define the ``Post`` model by using dynamic fields, as shown -in the following code: +Instead of using predefined fields, you can define the ``Post`` model by using +dynamic fields, as shown in the following code: .. code-block:: ruby :caption: app/models/post.rb @@ -196,9 +196,6 @@ in the following code: has_many :comments, dependent: :destroy end -{+odm+} does not use ActiveRecord migrations, because MongoDB does not -require a defined schema before you can store data. - Data Migration ~~~~~~~~~~~~~~ @@ -208,9 +205,9 @@ perform schema migration because MongoDB does not require a predefined schema to store the data. Migration tools are often specific to datasets. -Even though {+odm+} supports a superset of ActiveRecord associations, +Even though {+odm+} supports a superset of Active Record associations, the way that model references are stored in collections is different between -{+odm+} and ActiveRecord. +{+odm+} and Active Record. Visit the following resources to learn more about migrating from an RDBMS to MongoDB: diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index b05d1e18..8f3dc972 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -25,7 +25,7 @@ web application, connect to a MongoDB cluster hosted on MongoDB Atlas, and perform read and write operations on the data in your cluster. -To learn how to migrate an existing application to use {+odm+}, see the +To learn how to integrate {+odm+} into an existing application, see the :ref:`mongoid-add-to-existing` guide. .. tip:: @@ -41,7 +41,7 @@ create flexible data models. Ruby on Rails is a web application framework for {+language+}. Rails applications use a model-view-controller (MVC) architecture that allows you to easily control how your data is -modeled and displayed. {+odm+} replaces the default ActiveRecord +modeled and displayed. {+odm+} replaces the default Active Record adapter for data modeling in Rails. To learn more about Ruby on Rails, see the `Getting Started diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index 6bd7f25f..9814fdbc 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -63,9 +63,8 @@ gems to your web application. cd {+quickstart-rails-app-name+} The ``--skip-active-record`` flag instructs Rails to not add - ActiveRecord as a dependency. You don't need this - dependency because you will use {+odm+} - instead. + Active Record as a dependency. You don't need this + dependency because you will use {+odm+} instead. .. tip:: MacOS Installation Issue diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index b77aefaf..e0e053fd 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -24,7 +24,7 @@ This guide shows you how to use {+odm+} in a new Sinatra web application, connect to a MongoDB cluster hosted on MongoDB Atlas, and perform read and write operations on the data in your cluster. -To learn how to migrate an existing application to use {+odm+}, see the +To learn how to integrate {+odm+} into an existing application, see the :ref:`mongoid-add-to-existing` guide. .. tip:: From 772600ebb5b7a296193bb69c22423ee6be43589e Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 24 Oct 2024 11:28:17 -0400 Subject: [PATCH 032/113] fixes --- source/add-existing.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/add-existing.txt b/source/add-existing.txt index f8f257b2..cdbe23d2 100644 --- a/source/add-existing.txt +++ b/source/add-existing.txt @@ -31,20 +31,20 @@ Sinatra Application ------------------- To start using {+odm+} in an existing Sinatra application, you can follow -the steps described in the :ref:`mongoid-quick-start-sinatra` guide. - -The following steps describe how to add {+odm+} to a Sinatra application: +the following steps: 1. Add the ``mongoid`` dependency to your application's ``Gemfile``. #. Create a ``config/mongoid.yml`` configuration file and specify your - connection target. + connection target, as shown in the + :ref:`mongoid-quick-start-sinatra-connect-to-mongodb` step of the + Quick Start guide. #. Create an application file and load your configuration file, as shown in the :ref:`mongoid-quick-start-sinatra-view-data` step of the Quick Start guide. -#. Create {+odm+} models. +#. Create {+odm+} models to interact with your data. Rails Application ----------------- From 266a0036619e8caccf366bc68f370d66453e5fb6 Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 24 Oct 2024 15:34:48 -0400 Subject: [PATCH 033/113] DOCSP-42745: interact with data drawer --- source/index.txt | 1 + source/interact-data.txt | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 source/interact-data.txt diff --git a/source/index.txt b/source/index.txt index 31f5e279..87086789 100644 --- a/source/index.txt +++ b/source/index.txt @@ -14,6 +14,7 @@ MongoDB in Ruby. To work with {+odm+} from the command line using /quick-start-rails /quick-start-sinatra + /interact-data installation-configuration tutorials schema-configuration diff --git a/source/interact-data.txt b/source/interact-data.txt new file mode 100644 index 00000000..50cfcb8e --- /dev/null +++ b/source/interact-data.txt @@ -0,0 +1,16 @@ +.. _mongoid-interact-data: + +================== +Interact with Data +================== + +.. TODO + .. toctree:: + :caption: Interact with Data + /interact-data/specify-query + +In this section, you can learn how to use {+odm+} to interact with your +MongoDB data. + +.. - :ref:`mongoid-data-specify-query`: Learn about how to construct +.. queries to match specific documents in a MongoDB collection. From de317338151abe8fdfffbd53f8907701c3b881d2 Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 24 Oct 2024 15:35:50 -0400 Subject: [PATCH 034/113] tags --- source/interact-data.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/interact-data.txt b/source/interact-data.txt index 50cfcb8e..3ba2df35 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -4,6 +4,13 @@ Interact with Data ================== +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, crud, query + .. TODO .. toctree:: :caption: Interact with Data From 9a8b816bb6ec6ead0f00c520b248720a0ec3fc8e Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 24 Oct 2024 15:37:57 -0400 Subject: [PATCH 035/113] fix vale action --- .github/workflows/vale-tdbx.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/vale-tdbx.yml b/.github/workflows/vale-tdbx.yml index e33882b5..84de594a 100644 --- a/.github/workflows/vale-tdbx.yml +++ b/.github/workflows/vale-tdbx.yml @@ -12,27 +12,30 @@ jobs: - name: checkout uses: actions/checkout@v4 + - name: Install docutils + run: sudo apt-get install -y docutils + - id: files uses: masesgroup/retrieve-changed-files@v2 with: - format: 'csv' + format: "csv" - name: checkout-latest-rules uses: actions/checkout@v4 with: repository: mongodb/mongodb-vale-action - path: './tdbx-vale-rules' + path: "./tdbx-vale-rules" token: ${{secrets.GITHUB_TOKEN}} - name: move-files-for-vale-action run: | - cp tdbx-vale-rules/.vale.ini .vale.ini - mkdir -p .github/styles/ - cp -rf tdbx-vale-rules/.github/styles/ .github/ + cp tdbx-vale-rules/.vale.ini .vale.ini + mkdir -p .github/styles/ + cp -rf tdbx-vale-rules/.github/styles/ .github/ - name: run-vale uses: errata-ai/vale-action@reviewdog with: reporter: github-pr-check files: ${{steps.files.outputs.added_modified}} fail_on_error: true - token: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file + token: ${{secrets.GITHUB_TOKEN}} From 437ffdabda052bd563475cfadc4beea715456a14 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:37:54 -0400 Subject: [PATCH 036/113] remove extra word Co-authored-by: Nora Reidy --- source/interact-data.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/interact-data.txt b/source/interact-data.txt index 3ba2df35..424edf96 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -19,5 +19,5 @@ Interact with Data In this section, you can learn how to use {+odm+} to interact with your MongoDB data. -.. - :ref:`mongoid-data-specify-query`: Learn about how to construct +.. - :ref:`mongoid-data-specify-query`: Learn how to construct .. queries to match specific documents in a MongoDB collection. From c453ea2cb07cbcb2ee5175693563bb00632cc778 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 29 Oct 2024 10:36:30 -0400 Subject: [PATCH 037/113] DOCSP-42753: specify query part 1 --- snooty.toml | 3 +- source/includes/interact-data/query.rb | 161 +++++++++ source/interact-data.txt | 12 +- source/interact-data/specify-query.txt | 476 +++++++++++++++++++++++++ 4 files changed, 645 insertions(+), 7 deletions(-) create mode 100644 source/includes/interact-data/query.rb create mode 100644 source/interact-data/specify-query.txt diff --git a/snooty.toml b/snooty.toml index 17839cec..5ae4acc9 100644 --- a/snooty.toml +++ b/snooty.toml @@ -7,7 +7,8 @@ intersphinx = [ "https://www.mongodb.com/docs/manual/objects.inv", toc_landing_pages = [ "/quick-start-rails", - "/quick-start-sinatra" + "/quick-start-sinatra", + "/interact-data" ] [constants] diff --git a/source/includes/interact-data/query.rb b/source/includes/interact-data/query.rb new file mode 100644 index 00000000..2e6046fb --- /dev/null +++ b/source/includes/interact-data/query.rb @@ -0,0 +1,161 @@ +# start-simple-field-query +Band.where(name: 'Depeche Mode') +Band.where(name => 'Depeche Mode') +# end-simple-field-query + +# start-query-api-query +Band.where(founded: {'$gt' => 1980}) +Band.where('founded' => {'$gt' => 1980}) +# end-query-api-query + +# start-symbol-query +Band.where(:founded.gt => 1980) +# end-symbol-query + +# start-defined-field-query +Band.where(founded: '2020') +# end-defined-field-query + +# start-raw-field-query +Band.where(founded: Mongoid::RawValue('2020')) +# end-raw-field-query + +# start-id-field-query +Band.where(id: '5ebdeddfe1b83265a376a760') +# end-id-field-query + +# start-embedded-query +Band.where('manager.name' => 'Smith') +# end-embedded-query + +# start-embedded-ne-query +Band.where(:'manager.name'.ne => 'Smith') +# end-embedded-ne-query + +# start-logical-ops +# Uses "and" to combine criteria +Band.where(label: 'Trust in Trance').and(name: 'Astral Projection') + +# Uses "or" to specify criteria +Band.where(label: 'Trust in Trance').or(Band.where(name: 'Astral Projection')) + +# Uses "not" to specify criteria +Band.not(label: 'Trust in Trance', name: 'Astral Projection') + +# Uses "not" without arguments +Band.not.where(label: 'Trust in Trance', name: 'Astral Projection') +# end-logical-ops + +# start-logical-and-ops +# Conditions passed to separate "and" calls +Band.and(name: 'Sun Kil Moon').and(member_count: 2) + +# Multiple conditions in the same "and" call +Band.and({name: 'Sun Kil Moon'}, {member_count: 2}) + +# Multiple conditions in an array - Deprecates +Band.and([{name: 'Sun Kil Moon'}, {member_count: 2}]) + +# Condition in "where" and a scope +Band.where(name: 'Sun Kil Moon').and(Band.where(member_count: 2)) + +# Condition in "and" and a scope +Band.and({name: 'Sun Kil Moon'}, Band.where(member_count: 2)) + +# Scope as an array element, nested arrays - Deprecated +Band.and([Band.where(name: 'Sun Kil Moon'), [{member_count: 2}]]) +# end-logical-and-ops + +# start-logical-combination-ops +# Combines as "and" +Band.where(name: 'Swans').where(name: 'Feist').selector + +# Combines as "or" +Band.where(name: 'Swans').or(name: 'Feist').selector +# end-logical-combination-ops + +# start-logical-combination-ops-2 +# "or" applies to the first condition, and the second is combined +# as "and" +Band.or(name: 'Sun').where(label: 'Trust').selector + +# Same as previous example - where and and are aliases +Band.or(name: 'Sun').and(label: 'Trust').selector + +# Same operator can be stacked any number of times +Band.or(name: 'Sun').or(label: 'Trust').selector + +# The last label condition is added to the top level as "and" +Band.where(name: 'Sun').or(label: 'Trust').where(label: 'Feist').selector +# => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}], "label"=>"Foo"} +# end-logical-combination-ops-2 + +# start-not-logical +# "not" negates "where" +Band.not.where(name: 'Best').selector + +# The second "where" is added as "$and" +Band.not.where(name: 'Best').where(label: /Records/).selector + +# "not" negates its argument +Band.not(name: 'Best').selector +# end-not-logical + +# start-not-logical-note +# String negation - uses "$ne" +Band.not.where(name: 'Best').selector + +# Regex negation - uses "$not" +Band.not.where(name: /Best/).selector +# end-not-logical-note + +# start-not-behavior +# Simple condition +Band.not(name: /Best/).selector + +# Complex conditions +Band.where(name: /Best/).not(name: 'Astral Projection').selector + +# Symbol operator syntax +Band.not(:name.ne => 'Astral Projection') +# end-not-behavior + +# start-incremental-1 +Band.in(name: ['a']).in(name: ['b']) +# Interpreted query: +# {"name"=>{"$in"=>["a"]}, "$and"=>[{"name"=>{"$in"=>["b"]}}]} +# end-incremental-1 + +# start-in-merge +Band.in(name: ['a']).override.in(name: ['b']) +# Interpreted query: +# {"name"=>{"$in"=>["b"]}} + +Band.in(name: ['a', 'b']).intersect.in(name: ['b', 'c']) +# Interpreted query: +# {"name"=>{"$in"=>["b"]}} + +Band.in(name: ['a']).union.in(name: ['b']) +# Interpreted query: +# {"name"=>{"$in"=>["a", "b"]}} +# end-in-merge + +# start-merge-reset +Band.in(name: ['a']).union.ne(name: 'c').in(name: ['b']) +# Interpreted query: +# {"name"=>{"$in"=>["a"], "$ne"=>"c"}, "$and"=>[{"name"=>{"$in"=>["b"]}}]} +# end-merge-reset + +# start-merge-where +Band.in(name: ['a']).union.where(name: {'$in' => 'b'}) +# Interpreted query: +# {"foo"=>{"$in"=>["a"]}, "$and"=>[{"foo"=>{"$in"=>"b"}}]} +# end-merge-where + +# start-range-query +Band.in(year: 1950..1960) +# Interpreted query: +# {"year"=>{"$in"=>[1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960]}} +# end-range-query + + diff --git a/source/interact-data.txt b/source/interact-data.txt index 424edf96..83247dcb 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -11,13 +11,13 @@ Interact with Data .. meta:: :keywords: ruby framework, odm, crud, query -.. TODO - .. toctree:: - :caption: Interact with Data - /interact-data/specify-query +.. toctree:: + :caption: Interact with Data + + /interact-data/specify-query In this section, you can learn how to use {+odm+} to interact with your MongoDB data. -.. - :ref:`mongoid-data-specify-query`: Learn how to construct -.. queries to match specific documents in a MongoDB collection. +- :ref:`mongoid-data-specify-query`: Learn how to construct + queries to match specific documents in a MongoDB collection. diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt new file mode 100644 index 00000000..45270dab --- /dev/null +++ b/source/interact-data/specify-query.txt @@ -0,0 +1,476 @@ +.. _mongoid-data-specify-query: + +======= +Queries +======= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, crud, filter + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to specify a **query** by using {+odm+}. + +You can refine the set of documents that a query returns by creating a +**query filter**. A query filter is an expression that specifies the search +criteria MongoDB uses to match documents in a read or write operation. +When creating a query filter, you can prompt the driver to search for +documents with an exact match to your query, or you can compose query +filters to express more complex matching criteria. + +{+odm+} provides a query DSL similar to the one used in Active Record. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``Band`` model, which represents a +band or musical group. The definition of the ``Band`` model might be +different for each section to demonstrate different query +functionalities. Some sections might also use the ``Manager`` model, +which represents a person who manages a given band. + +``Show`` model, which +represents a live performance by a certain band or musical group. + +Queries in {+odm+} +------------------ + +{+odm+} query methods return ``Mongoid::Criteria`` objects, which are +chainable and lazily evaluated wrappers for the MongoDB Query API. +The queries are executed when you iterate through the results. The +following example demonstrates the return type for a simple query: + +.. code-block:: ruby + + # Creates a simple query + Band.where(name: "Deftones") + + # Returns a Criteria object + # => #"Deftones"} + # options: {} + # class: Band + # embedded: false> + + # Evaluate the query by converting to JSON + Band.where(name: "Deftones").to_json + + # Returns matching documents + # => [{"_id":"...","name":"Deftones"}] + +You can use methods such as ``first()`` and ``last()`` to return +individual documents. You can also iterate a ``Criteria`` object by using +methods such as ``each()`` or ``map()`` to retrieve documents from the +server. You can use ``to_json()`` to convert a ``Criteria`` object to +JSON. + +.. tip:: + + If you call more query methods on an existing ``Criteria`` object, + {+odm+} merges the filter criteria. + +Condition Syntax +---------------- + +This section describes the syntax patterns that you can use to create +filter criteria. You can specify queries in {+odm+} by using any of the +following syntax patterns: + +- Field syntax +- Query API syntax +- Symbol operator syntax + +.. note:: Query Syntax Behavior + + These syntaxes support querying embedded documents by using dot notation. + The syntaxes also respect :ref:`field aliases ` + and field types, if the field being queried is defined in the model class. + +The examples in this section use the following model definition: + +.. code-block:: ruby + + class Band + include Mongoid::Document + + field :name, type: String + field :founded, type: Integer + field :m, as: :member_count, type: Integer + + embeds_one :manager + end + + class Manager + include Mongoid::Document + + embedded_in :band + + field :name, type: String + end + +Field Syntax +~~~~~~~~~~~~ + +The field querying syntax uses the basic {+language+} hashes. The keys +can be symbols or strings and correspond to field names in MongoDB +documents. + +The following code shows two equivalent queries that use field querying +syntax to retrieve documents in which the ``name`` field value is +``'Depeche Mode'``: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-simple-field-query + :end-before: end-simple-field-query + :language: ruby + :dedent: + +Query API Syntax +~~~~~~~~~~~~~~~~ + +You can specify a Query API operator on any field by using the hash +syntax, as shown by the following equivalent queries: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-query-api-query + :end-before: end-query-api-query + :language: ruby + :dedent: + +Symbol Operator Syntax +~~~~~~~~~~~~~~~~~~~~~~ + +You can specify Query API operators as methods on symbols for the +respective field name, as shown in the following code: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-symbol-query + :end-before: end-symbol-query + :language: ruby + :dedent: + +Fields +------ + +This section describes how to perform queries on fields with different +types of values. + +Defined Fields +~~~~~~~~~~~~~~ + +.. TODO add link to Fields page + +To query on a field, the field does not need to be in the +the model class definition. However, if a field is defined in +the model class, {+odm+} coerces query values to match the defined field +types when constructing the query. + +The following code specifies a string value when querying on the +``founded`` field. Because the ``founded`` field is defined in the model +class to have ``Integer`` values, {+odm+} coerces the string ``'2020'`` +to ``2020`` when performing the query: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-defined-field-query + :end-before: end-defined-field-query + :language: ruby + :dedent: + +Raw Values +~~~~~~~~~~ + +To bypass {+odm+}'s query type coercion behavior and query +directly for the raw-typed value in the database, wrap the query value in +the ``Mongoid::RawValue`` class, as shown in the following code: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-raw-field-query + :end-before: end-raw-field-query + :language: ruby + :dedent: + +This behavior can be useful when working with legacy data. + +.. _mongoid-query-field-alias: + +Field Aliases +~~~~~~~~~~~~~ + +.. TODO update links + +Queries follow the :ref:`storage field names ` +and :ref:`field aliases ` that you might have set in your +model class definition. + +The ``id`` and ``_id`` fields are aliases, so you can use either field +name in queries: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-id-field-query + :end-before: end-id-field-query + :language: ruby + :dedent: + +Embedded Documents +~~~~~~~~~~~~~~~~~~ + +To query on values of fields of embedded documents, you can use dot +notation. The following code retrieves documents in which the ``name`` +field of the embedded ``Manager`` document is ``'Smith'``: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-embedded-query + :end-before: end-embedded-query + :language: ruby + :dedent: + +The following code demonstrates how to use a symbol operator when +querying on an embedded field: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-embedded-ne-query + :end-before: end-embedded-ne-query + :language: ruby + :dedent: + +.. note:: + + Queries always return top-level model instances, even if all of the + conditions reference embedded document fields. + +.. _mongoid-query-logical-operations: + +Logical Operations +------------------ + +{+odm+} supports ``and()``, ``or()``, ``nor()``, and ``not()`` logical +operations on ``Criteria`` objects. These methods take one or more hashes +of conditions or another ``Criteria`` object as their arguments. The +``not`` operation has an argument-free version. + +The following code demonstrates how to use the logical operations in +queries: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-logical-ops + :end-before: end-logical-ops + :language: ruby + :dedent: + +.. note:: Array Parameters + + To ensure backwards compatibility with earlier {+odm+} versions, the + logical operation methods accept arrays of parameters, which are + flattened to obtain the criteria. + + Passing arrays to logical operations is deprecated and may be removed + in a future version. + +The following queries produce the same conditions: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-logical-and-ops + :end-before: end-logical-and-ops + :language: ruby + :dedent: + +Operator Combinations +~~~~~~~~~~~~~~~~~~~~~ + +The logical operators have the the same semantics as those from Active +Record. + +When conditions are specified on the same field multiple times, all +conditions are added to the criteria, as shown by the queries in the +following code: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-logical-combination-ops + :end-before: end-logical-combination-ops + :language: ruby + :dedent: + +The ``any_of()``, ``none_of()``, ``nor()``, and ``not()`` operations +behave similarly. + +When you use ``and()``, ``or()``, and ``nor()`` logical operators, they +operate on the criteria built up to that point: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-logical-combination-ops-2 + :end-before: end-logical-combination-ops-2 + :language: ruby + :dedent: + +not() Behavior +~~~~~~~~~~~~~~ + +You can use the ``not()`` method without arguments, in which case it +negates the next condition that is specified. The ``not()`` method can +be called with one or more hash conditions or ``Criteria`` objects, +which are all negated and added to the criteria. + +The following examples demonstrate the behavior of ``not()``: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-not-logical + :end-before: end-not-logical + :language: ruby + :dedent: + +.. note:: + + You cannot use the ``$not`` operator in MongoDB with a string argument. + Mongoid uses the ``$ne`` operator to achieve negation: + + .. literalinclude:: /includes/interact-data/query.rb + :start-after: start-not-logical-note + :end-before: end-not-logical-note + :language: ruby + :dedent: + +Similarly to ``and()``, the ``not()`` operation negates individual +conditions for simple field criteria. For complex conditions and when a +field already has a condition defined on it, {+odm+} emulates ``$not`` +by using an ``{'$and' => [{'$nor' => ...}]}`` construct, since MongoDB +only supports the ``$not`` operator on a per-field basis rather than +globally: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-not-behavior + :end-before: end-not-behavior + :language: ruby + :dedent: + +If you are using ``not()`` with arrays or regular expressions, view the +limitations of ``$not`` in the :manual:`{+server-manual+} +`. + +Incremental Query Construction +------------------------------ + +By default, when you add conditions to a query, {+odm+} considers each +condition complete and independent from any other conditions +present in the query. For example, calling ``in()`` twice adds two separate +``$in`` conditions: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-incremental-1 + :end-before: end-incremental-1 + :language: ruby + :dedent: + +Some operator methods support building the condition incrementally. In this +case, when adding a condition which uses one of the supported operators, +if there already is a condition on the same field using the +same operator, the operator expressions are combined according to the +specified *merge strategy*, which is described in the following section. + +.. _mongoid-merge-strategies: + +Merge Strategies +~~~~~~~~~~~~~~~~ + +{+odm+} provides the following merge strategies: + +- **Override**: The new operator instance replaces any existing + conditions on the same field by using the same operator. +- **Intersect**: If there already is a condition using the same operator + on the same field, the values of the existing condition are + intersected with the values of the new condition and the result is + stored as the operator value. +- **Union**: If there already is a condition using the same operator on + the same field, the values of the new condition are added to the + values of the existing condition and the result is stored as the + operator value. + +The following code demonstrates hwo the merge strategies produce +criteria, by using ``in()`` as the example operator: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-in-merge + :end-before: end-in-merge + :language: ruby + :dedent: + +The strategy is requested by calling ``override()``, ``intersect()`` or ``union()`` +on a ``Criteria`` instance. The requested strategy applies to the next +condition method called on the query. If the next condition method called does +not support merge strategies, the strategy is reset, as shown in the following +example: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-merge-reset + :end-before: end-merge-reset + :language: ruby + :dedent: + +Since ``ne()`` does not support merge strategies, the ``union`` strategy +is ignored and reset, then when ``in()`` is invoked the second time, +there is no active strategy. + +.. warning:: + + Merge strategies assume that the previous conditions have been added + to the top level of the query. However, this is not always the case, + as conditions may be nested under an ``$and`` clause. Using merge + strategies with complex criteria generate incorrect queries. + +Supported Operator Methods +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following operator methods support merge strategies: + +- ``all()`` +- ``in()`` +- ``nin()`` + +The set of methods may be expanded in future releases of {+odm+}. To ensure +future compatibility, invoke a strategy method only when the next method call +is an operator that supports merge strategies. + +Merge strategies are currently applied only when conditions are +added through the designated methods. In the following example, the +merge strategy is not applied because the second condition is added as +``where()``, not by using ``in()``: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-merge-where + :end-before: end-merge-where + :language: ruby + :dedent: + +Operator Value Expansion +~~~~~~~~~~~~~~~~~~~~~~~~ + +Operator methods that support merge strategies take ``Array`` as their +value type. {+odm+} expands ``Array``-compatible types, such as a +``Range``, when they are used with these operator methods. + +The following example demonstrates how you can pass a ``Range`` object +as the query value in when calling ``in()``: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-range-query + :end-before: end-range-query + :language: ruby + :dedent: + +{+odm+} wraps non-``Array`` values in arrays, +as the shown in the following example: + +.. code-block:: ruby + + Band.in(year: 1950) + # Interpreted query: {"year"=>{"$in"=>[1950]}} From d8469d9d5be5c09816f74400b42ce4a472d20e34 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 29 Oct 2024 10:38:40 -0400 Subject: [PATCH 038/113] vale --- source/interact-data/specify-query.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 45270dab..f8600413 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -246,7 +246,7 @@ querying on an embedded field: .. note:: - Queries always return top-level model instances, even if all of the + Queries always return top-level model instances, even if all the conditions reference embedded document fields. .. _mongoid-query-logical-operations: @@ -440,7 +440,7 @@ The set of methods may be expanded in future releases of {+odm+}. To ensure future compatibility, invoke a strategy method only when the next method call is an operator that supports merge strategies. -Merge strategies are currently applied only when conditions are +Merge strategies are applied only when conditions are added through the designated methods. In the following example, the merge strategy is not applied because the second condition is added as ``where()``, not by using ``in()``: From 5fa165a497132c3c0344fccd37b45ff7dac03cc3 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 29 Oct 2024 10:39:49 -0400 Subject: [PATCH 039/113] title --- source/interact-data/specify-query.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index f8600413..bdd74d76 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -1,8 +1,8 @@ .. _mongoid-data-specify-query: -======= -Queries -======= +=============== +Specify a Query +=============== .. facet:: :name: genre From 4b4e05835cefc09ae71851f45a61acd7ef249924 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 29 Oct 2024 12:25:17 -0400 Subject: [PATCH 040/113] code edits --- source/includes/interact-data/query.rb | 33 +++++++++++++------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/source/includes/interact-data/query.rb b/source/includes/interact-data/query.rb index 2e6046fb..a92317ef 100644 --- a/source/includes/interact-data/query.rb +++ b/source/includes/interact-data/query.rb @@ -1,6 +1,6 @@ # start-simple-field-query Band.where(name: 'Depeche Mode') -Band.where(name => 'Depeche Mode') +Band.where('name' => 'Depeche Mode') # end-simple-field-query # start-query-api-query @@ -68,53 +68,54 @@ # start-logical-combination-ops # Combines as "and" -Band.where(name: 'Swans').where(name: 'Feist').selector +Band.where(name: 'Swans').where(name: 'Feist') # Combines as "or" -Band.where(name: 'Swans').or(name: 'Feist').selector +Band.where(name: 'Swans').or(name: 'Feist') # end-logical-combination-ops # start-logical-combination-ops-2 # "or" applies to the first condition, and the second is combined # as "and" -Band.or(name: 'Sun').where(label: 'Trust').selector +Band.or(name: 'Sun').where(label: 'Trust') -# Same as previous example - where and and are aliases -Band.or(name: 'Sun').and(label: 'Trust').selector +# Same as previous example - "where" and "and" are aliases +Band.or(name: 'Sun').and(label: 'Trust') # Same operator can be stacked any number of times -Band.or(name: 'Sun').or(label: 'Trust').selector +Band.or(name: 'Sun').or(label: 'Trust') # The last label condition is added to the top level as "and" -Band.where(name: 'Sun').or(label: 'Trust').where(label: 'Feist').selector -# => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}], "label"=>"Foo"} +Band.where(name: 'Sun').or(label: 'Trust').where(label: 'Feist') +# Interpreted query: +# {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}], "label"=>"Feist"} # end-logical-combination-ops-2 # start-not-logical # "not" negates "where" -Band.not.where(name: 'Best').selector +Band.not.where(name: 'Best') # The second "where" is added as "$and" -Band.not.where(name: 'Best').where(label: /Records/).selector +Band.not.where(name: 'Best').where(label: /Records/) # "not" negates its argument -Band.not(name: 'Best').selector +Band.not(name: 'Best') # end-not-logical # start-not-logical-note # String negation - uses "$ne" -Band.not.where(name: 'Best').selector +Band.not.where(name: 'Best') # Regex negation - uses "$not" -Band.not.where(name: /Best/).selector +Band.not.where(name: /Best/) # end-not-logical-note # start-not-behavior # Simple condition -Band.not(name: /Best/).selector +Band.not(name: /Best/) # Complex conditions -Band.where(name: /Best/).not(name: 'Astral Projection').selector +Band.where(name: /Best/).not(name: 'Astral Projection') # Symbol operator syntax Band.not(:name.ne => 'Astral Projection') From 6b3840831109c9e2f241f8612ed2b10b76441f6f Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 29 Oct 2024 14:02:24 -0400 Subject: [PATCH 041/113] MW PR fixes 2 --- source/add-existing.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/add-existing.txt b/source/add-existing.txt index cdbe23d2..ae75633a 100644 --- a/source/add-existing.txt +++ b/source/add-existing.txt @@ -30,7 +30,7 @@ application that uses {+odm+}, see one of the following guides: Sinatra Application ------------------- -To start using {+odm+} in an existing Sinatra application, you can follow +To start using {+odm+} in an existing Sinatra application, perform the following steps: 1. Add the ``mongoid`` dependency to your application's ``Gemfile``. @@ -103,7 +103,7 @@ Modify Frameworks Open the ``config/application.rb`` file and examine the contents. If the file uses the ``require "rails/all"`` statement to load all Rails components, delete this statement. You must add a separate ``require`` statement -for each Rails component, as shown the following sample +for each Rails component, as shown in the following sample ``config/application.rb`` file: .. code-block:: ruby @@ -206,8 +206,8 @@ a predefined schema to store the data. Migration tools are often specific to datasets. Even though {+odm+} supports a superset of Active Record associations, -the way that model references are stored in collections is different between -{+odm+} and Active Record. +model references are stored differently in collections when using +{+odm+} compared to Active Record. Visit the following resources to learn more about migrating from an RDBMS to MongoDB: From ef7bd6997a53aadd885020b8f10c01a75f4dfd27 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 30 Oct 2024 13:44:55 -0400 Subject: [PATCH 042/113] DOCSP-44849: modify results --- snooty.toml | 1 + .../includes/interact-data/modify-results.rb | 34 +++ source/interact-data.txt | 11 +- source/interact-data/modify-results.txt | 279 ++++++++++++++++++ 4 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 source/includes/interact-data/modify-results.rb create mode 100644 source/interact-data/modify-results.txt diff --git a/snooty.toml b/snooty.toml index 17839cec..fd8e10ea 100644 --- a/snooty.toml +++ b/snooty.toml @@ -22,3 +22,4 @@ quickstart-sinatra-app-name = "my-sinatra-app" quickstart-rails-app-name = "my-rails-app" feedback-widget-title = "Feedback" server-manual = "Server manual" +api = "https://www.mongodb.com/docs/mongoid/master/api" \ No newline at end of file diff --git a/source/includes/interact-data/modify-results.rb b/source/includes/interact-data/modify-results.rb new file mode 100644 index 00000000..fb45192c --- /dev/null +++ b/source/includes/interact-data/modify-results.rb @@ -0,0 +1,34 @@ +# start-only +Band.where(members: 4).only(:name) +# end-only + +# start-only-embed +bands = Band.only(:name, 'tours.year') +# end-only-embed + +# start-only-embed-association +# Returns null +Band.where(name: 'Astral Projection').only(:name).first.managers + +# Returns the first Manager object +Band.where(name: 'Astral Projection').only(:name, :manager_ids).first.managers +# end-only-embed-association + +# start-without +Band.where(members: 4).without(:year) +# end-without + +# start-limit +Band.limit(5) +# end-limit + +# start-skip +Band.skip(3) + +# Equivalent +Band.offset(3) +# end-skip + +# start-batch +Band.batch_size(500) +# end-batch diff --git a/source/interact-data.txt b/source/interact-data.txt index 424edf96..224183ce 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -11,13 +11,16 @@ Interact with Data .. meta:: :keywords: ruby framework, odm, crud, query -.. TODO - .. toctree:: - :caption: Interact with Data - /interact-data/specify-query +.. toctree:: + :caption: Interact with Data + + /interact-data/modify-results In this section, you can learn how to use {+odm+} to interact with your MongoDB data. +- :ref:`mongoid-data-modify-results`: Learn how to modify the way that + {+odm+} returns results from queries. + .. - :ref:`mongoid-data-specify-query`: Learn how to construct .. queries to match specific documents in a MongoDB collection. diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt new file mode 100644 index 00000000..51554ac4 --- /dev/null +++ b/source/interact-data/modify-results.txt @@ -0,0 +1,279 @@ +.. _mongoid-data-modify-results: + +==================== +Modify Query Results +==================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, crud, print results + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to customize the way that {+odm+} +returns results to you from queries. MongoDB allows you to perform the +following actions to modify the way that results appear: + +- :ref:`mongoid-data-projection` + +- :ref:`mongoid-data-sort` + +- :ref:`mongoid-data-skip-limit` + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``Band`` model, which represents a +band or musical group. The definition of the ``Band`` model might be +different for each section to demonstrate different query +functionalities. + +.. _mongoid-data-projection: + +Return Specified Fields +----------------------- + +In MongoDB, the process of specifying fields to include or exclude from +results is called *projection*. {+odm+} provides the following operators +to project fields: + +- ``only()``: Specifies fields to include +- ``without()``: Specifies fields to exclude + +Include Fields +~~~~~~~~~~~~~~ + +The ``only()`` method retrieves only the specified fields from the +database. + +The following code returns only the ``name`` field from documents in +which the value of the ``members`` field is ``4``: + +.. literalinclude:: /includes/interact-data/modify-results.rb + :start-after: start-only + :end-before: end-only + :language: ruby + :dedent: + +.. note:: _id Field + + In MongoDB, the ``_id`` field is included in results even if you do + not explicitly include it. + +If you attempt to reference attributes that have not been loaded, +{+odm+} raises a ``Mongoid::Errors::AttributeNotLoaded`` error. + +You can also use the ``only()`` method to include fields from embedded +documents. + +Consider that the ``Band`` model embeds multiple ``Tour`` objects. You can +project fields from the ``Tour`` model such as ``year``, as shown in the +following code: + +.. literalinclude:: /includes/interact-data/modify-results.rb + :start-after: start-only-embed + :end-before: end-only-embed + :language: ruby + :dedent: + +Then, you can access the embedded fields from the returned documents: + +.. code-block:: ruby + + # Returns the first Tour object from + # the first Band in the results + bands.first.tours.first + +You can pass fields of referenced associations to the ``only()`` method, +but the projection is ignored when loading the embedded objects. {+odm+} +loads all fields of the referenced associations. + +.. note:: + + If you are connected to a deployment running MongoDB 4.4 or higher, + you cannot specify an association and its fields in a projection in + the same query. + +If a document has ``has_one`` or ``has_and_belongs_to_many`` +associations, you must include the fields with foreign keys in the list +of attributes loaded when using ``only()`` for those associations to be +loaded. + +In the following example, the ``Band`` and ``Manager`` models have a +``has_and_belongs_to_many`` association: + +.. code-block:: ruby + + class Band + include Mongoid::Document + field :name, type: String + has_and_belongs_to_many :managers + end + + class Manager + include Mongoid::Document + has_and_belongs_to_many :bands + end + +The following code demonstrates how {+odm+} can load the associated +``Manager`` objects if you include the ``manager_ids`` field: + +.. literalinclude:: /includes/interact-data/modify-results.rb + :start-after: start-only-embed-association + :end-before: end-only-embed-association + :language: ruby + :dedent: + +Exclude Fields +~~~~~~~~~~~~~~ + +You can explicitly exclude fields from results by using the +``without()`` method. + +The following code excludes the ``year`` field from returned ``Band`` +objects: + +.. literalinclude:: /includes/interact-data/modify-results.rb + :start-after: start-without + :end-before: end-without + :language: ruby + :dedent: + +.. important:: _id Field + + {+odm+} requires the ``_id`` field for various operations, so you + *cannot* exclude the ``_id`` field or the ``id`` alias from results. + If you pass ``_id`` or ``id`` to the ``without()`` method, {+odm+} + ignores it. + +.. _mongoid-data-sort: + +Sort Results +------------ + +You can sort the order that {+odm+} returns documents by using the +``order()`` and ``order_by()`` methods. + +These methods accept a hash that indicates which fields to order the +documents by, and whether to use an ascending or descending order for +each field. + +You can specify the sort direction in the following ways: + +- Integers ``1`` (ascending) and ``-1`` (descending) + - Example: ``Band.order(name: 1, year: -1)`` + +- Symbols ``:asc`` and ``:desc`` + - Example: ``Band.order(name: :asc)`` + +- Strings ``"asc"`` and ``"desc"`` + - Example: ``Band.order_by(name: "asc", year: "desc")`` + +The ``order()`` method also accepts the following sort specifications: + +- Array of two-element arrays: + + - Strings + - Example: ``Band.order([['name', 'asc'], ['year', 'desc']])`` + + - Symbols + - Example: ``Band.order([[:name, :asc]])`` + +- ``asc`` and ``desc`` methods on symbols + - Example: ``Band.order(:name.asc, :year.desc)`` + +- SQL syntax + - Example: ``Band.order('name desc')`` + +You can also use the ``asc()`` and ``desc()`` methods instead of using +``order()``: + +.. code-block:: ruby + + Band.asc('name').desc('year') + +If you chain sort specifications, the first call defines the most +significant criteria and the newest call defines the least significant +one. + +.. TODO update link in the following note for scope + +.. note:: Sorting in Scopes + + If you define a scope on your model that includes a sort specification, + the scope sort takes precedence over the sort specified in a query, as the + default scope is evaluated first. + +.. _mongoid-data-skip-limit: + +Paginate Results +---------------- + +{+odm+} provides the ``limit()``, ``skip()``, and ``batch_size()`` +pagination operators that you can use on ``Criteria`` objects. The +following sections describe how to use these operators. + +Limit Number of Results +~~~~~~~~~~~~~~~~~~~~~~~ + +You can limit the number of results that {+odm+} returns by using the +``limit()`` method. + +The following code retrieves a maximum of ``5`` documents: + +.. literalinclude:: /includes/interact-data/modify-results.rb + :start-after: start-limit + :end-before: end-limit + :language: ruby + :dedent: + +Skip Results +~~~~~~~~~~~~ + +You can skip a specified number of results by using the ``skip()`` +method, or its alias ``offset()``. If you chain a ``limit()`` call, it +is applied after documents are skipped. + +.. tip:: + + When performing pagination, you should use ``skip()`` on :ref:`sorted results ` to ensure consistent results. + +The following code skips the first ``3`` documents when returning results: + +.. literalinclude:: /includes/interact-data/modify-results.rb + :start-after: start-skip + :end-before: end-skip + :language: ruby + :dedent: + +Generate Batches of Results +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When executing large queries and when iterating over query results by using +an enumerator method such as ``Criteria#each()``, {+odm+} automatically +uses the MongoDB :manual:`getMore ` command +to load results in batches. The default batch size is ``1000``, but +you can set a different value by using the ``batch_size()`` method. + +The following code sets the batch size to ``500``: + +.. literalinclude:: /includes/interact-data/modify-results.rb + :start-after: start-batch + :end-before: end-batch + :language: ruby + :dedent: + +Additional Information +---------------------- + +.. TODO: add links to the bottom of this page From 1f16e11dda08cf3626012e8fc31022b1b95c79dd Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 30 Oct 2024 13:46:41 -0400 Subject: [PATCH 043/113] vale --- source/interact-data/modify-results.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt index 51554ac4..72d1cbfe 100644 --- a/source/interact-data/modify-results.txt +++ b/source/interact-data/modify-results.txt @@ -100,7 +100,7 @@ loads all fields of the referenced associations. .. note:: - If you are connected to a deployment running MongoDB 4.4 or higher, + If you are connected to a deployment running MongoDB 4.4 or later, you cannot specify an association and its fields in a projection in the same query. @@ -246,7 +246,7 @@ is applied after documents are skipped. .. tip:: - When performing pagination, you should use ``skip()`` on :ref:`sorted results ` to ensure consistent results. + When performing pagination, use ``skip()`` on :ref:`sorted results ` to ensure consistent results. The following code skips the first ``3`` documents when returning results: From 7a5479f0f3de1e4b11e94c1ea7db80227ea01888 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 30 Oct 2024 14:03:42 -0400 Subject: [PATCH 044/113] JS PR fixes 1 --- source/includes/interact-data/query.rb | 3 +- source/interact-data/specify-query.txt | 71 ++++++++++++++------------ 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/source/includes/interact-data/query.rb b/source/includes/interact-data/query.rb index a92317ef..d2aa70b5 100644 --- a/source/includes/interact-data/query.rb +++ b/source/includes/interact-data/query.rb @@ -22,6 +22,7 @@ # start-id-field-query Band.where(id: '5ebdeddfe1b83265a376a760') +Band.where(_id: '5ebdeddfe1b83265a376a760') # end-id-field-query # start-embedded-query @@ -53,7 +54,7 @@ # Multiple conditions in the same "and" call Band.and({name: 'Sun Kil Moon'}, {member_count: 2}) -# Multiple conditions in an array - Deprecates +# Multiple conditions in an array - Deprecated Band.and([{name: 'Sun Kil Moon'}, {member_count: 2}]) # Condition in "where" and a scope diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index bdd74d76..d09c0923 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -9,7 +9,7 @@ Specify a Query :values: reference .. meta:: - :keywords: ruby framework, odm, crud, filter + :keywords: ruby framework, odm, crud, filter, code example .. contents:: On this page :local: @@ -29,7 +29,8 @@ When creating a query filter, you can prompt the driver to search for documents with an exact match to your query, or you can compose query filters to express more complex matching criteria. -{+odm+} provides a query DSL similar to the one used in Active Record. +{+odm+} provides a query domain-specific language (DSL) similar to the +one used in Active Record. Sample Data ~~~~~~~~~~~ @@ -38,9 +39,7 @@ The examples in this guide use the ``Band`` model, which represents a band or musical group. The definition of the ``Band`` model might be different for each section to demonstrate different query functionalities. Some sections might also use the ``Manager`` model, -which represents a person who manages a given band. - -``Show`` model, which +which represents a person who manages a given band, or a ``Show`` model, which represents a live performance by a certain band or musical group. Queries in {+odm+} @@ -75,13 +74,13 @@ methods such as ``each()`` or ``map()`` to retrieve documents from the server. You can use ``to_json()`` to convert a ``Criteria`` object to JSON. -.. tip:: +.. tip:: Chaining methods - If you call more query methods on an existing ``Criteria`` object, + If you chain other query methods on an existing ``Criteria`` object, {+odm+} merges the filter criteria. -Condition Syntax ----------------- +Create a Query Filter +--------------------- This section describes the syntax patterns that you can use to create filter criteria. You can specify queries in {+odm+} by using any of the @@ -91,7 +90,7 @@ following syntax patterns: - Query API syntax - Symbol operator syntax -.. note:: Query Syntax Behavior +.. note:: Syntax Behaviors These syntaxes support querying embedded documents by using dot notation. The syntaxes also respect :ref:`field aliases ` @@ -160,8 +159,8 @@ respective field name, as shown in the following code: :language: ruby :dedent: -Fields ------- +Query on Different Field Types +------------------------------ This section describes how to perform queries on fields with different types of values. @@ -200,8 +199,6 @@ the ``Mongoid::RawValue`` class, as shown in the following code: :language: ruby :dedent: -This behavior can be useful when working with legacy data. - .. _mongoid-query-field-alias: Field Aliases @@ -254,10 +251,17 @@ querying on an embedded field: Logical Operations ------------------ -{+odm+} supports ``and()``, ``or()``, ``nor()``, and ``not()`` logical -operations on ``Criteria`` objects. These methods take one or more hashes -of conditions or another ``Criteria`` object as their arguments. The -``not`` operation has an argument-free version. +{+odm+} supports the following logical operations on ``Criteria`` +objects: + +- ``and()`` +- ``or()`` +- ``nor()`` +- ``not()`` + +These methods take one or more hashes of conditions or another +``Criteria`` object as their arguments. The ``not()`` operation has an +argument-free version. The following code demonstrates how to use the logical operations in queries: @@ -274,7 +278,7 @@ queries: logical operation methods accept arrays of parameters, which are flattened to obtain the criteria. - Passing arrays to logical operations is deprecated and may be removed + Passing arrays to logical operations is deprecated and might be removed in a future version. The following queries produce the same conditions: @@ -343,8 +347,8 @@ The following examples demonstrate the behavior of ``not()``: Similarly to ``and()``, the ``not()`` operation negates individual conditions for simple field criteria. For complex conditions and when a field already has a condition defined on it, {+odm+} emulates ``$not`` -by using an ``{'$and' => [{'$nor' => ...}]}`` construct, since MongoDB -only supports the ``$not`` operator on a per-field basis rather than +by using an ``{'$and' => [{'$nor' => ...}]}`` construct, because MongoDB +supports the ``$not`` operator only on a per-field basis rather than globally: .. literalinclude:: /includes/interact-data/query.rb @@ -371,11 +375,12 @@ present in the query. For example, calling ``in()`` twice adds two separate :language: ruby :dedent: -Some operator methods support building the condition incrementally. In this -case, when adding a condition which uses one of the supported operators, -if there already is a condition on the same field using the -same operator, the operator expressions are combined according to the -specified *merge strategy*, which is described in the following section. +Some operator methods support building the condition incrementally. When +you add a condition which uses one of the supported operators, {+odm+} +sees if there already is a condition on the same field using the +same operator. If so, the operator expressions are combined according to the +specified *merge strategy*. The following section describes the available merge +strategies. .. _mongoid-merge-strategies: @@ -395,8 +400,8 @@ Merge Strategies values of the existing condition and the result is stored as the operator value. -The following code demonstrates hwo the merge strategies produce -criteria, by using ``in()`` as the example operator: +The following code demonstrates how the merge strategies produce +criteria by using ``in()`` as the example operator: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-in-merge @@ -416,7 +421,7 @@ example: :language: ruby :dedent: -Since ``ne()`` does not support merge strategies, the ``union`` strategy +Because ``ne()`` does not support merge strategies, the ``union`` strategy is ignored and reset, then when ``in()`` is invoked the second time, there is no active strategy. @@ -424,8 +429,8 @@ there is no active strategy. Merge strategies assume that the previous conditions have been added to the top level of the query. However, this is not always the case, - as conditions may be nested under an ``$and`` clause. Using merge - strategies with complex criteria generate incorrect queries. + as conditions might be nested under an ``$and`` clause. Using merge + strategies with complex criteria can generate incorrect queries. Supported Operator Methods ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -436,7 +441,7 @@ The following operator methods support merge strategies: - ``in()`` - ``nin()`` -The set of methods may be expanded in future releases of {+odm+}. To ensure +The set of methods might be expanded in future releases of {+odm+}. To ensure future compatibility, invoke a strategy method only when the next method call is an operator that supports merge strategies. @@ -459,7 +464,7 @@ value type. {+odm+} expands ``Array``-compatible types, such as a ``Range``, when they are used with these operator methods. The following example demonstrates how you can pass a ``Range`` object -as the query value in when calling ``in()``: +as the query value when using the ``in()`` method: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-range-query From f722f7796025ef6586b86c18c1f30f185b1e9171 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 30 Oct 2024 14:04:24 -0400 Subject: [PATCH 045/113] fix --- source/interact-data/specify-query.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index d09c0923..63ed1b74 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -422,7 +422,7 @@ example: :dedent: Because ``ne()`` does not support merge strategies, the ``union`` strategy -is ignored and reset, then when ``in()`` is invoked the second time, +is ignored and reset. Then, when ``in()`` is invoked the second time, there is no active strategy. .. warning:: From 5c1c6ee08d3a232add31c98869a1b08ff85ce1ae Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 30 Oct 2024 14:06:42 -0400 Subject: [PATCH 046/113] fix --- source/interact-data/modify-results.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt index 72d1cbfe..9533dd77 100644 --- a/source/interact-data/modify-results.txt +++ b/source/interact-data/modify-results.txt @@ -9,7 +9,7 @@ Modify Query Results :values: reference .. meta:: - :keywords: ruby framework, odm, crud, print results + :keywords: ruby framework, odm, crud, print results, code example .. contents:: On this page :local: @@ -36,7 +36,9 @@ Sample Data The examples in this guide use the ``Band`` model, which represents a band or musical group. The definition of the ``Band`` model might be different for each section to demonstrate different query -functionalities. +functionalities. Some sections might also use the ``Manager`` model, +which represents a person who manages a given band, or a ``Tour`` model, which +represents live performances by a certain band or musical group. .. _mongoid-data-projection: From e647538f40fb4a45bf8581f52f93a01695e1c043 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 30 Oct 2024 17:29:22 -0400 Subject: [PATCH 047/113] list fixes --- source/interact-data/modify-results.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt index 9533dd77..45fabed2 100644 --- a/source/interact-data/modify-results.txt +++ b/source/interact-data/modify-results.txt @@ -173,12 +173,15 @@ each field. You can specify the sort direction in the following ways: - Integers ``1`` (ascending) and ``-1`` (descending) + - Example: ``Band.order(name: 1, year: -1)`` - Symbols ``:asc`` and ``:desc`` + - Example: ``Band.order(name: :asc)`` - Strings ``"asc"`` and ``"desc"`` + - Example: ``Band.order_by(name: "asc", year: "desc")`` The ``order()`` method also accepts the following sort specifications: @@ -186,15 +189,19 @@ The ``order()`` method also accepts the following sort specifications: - Array of two-element arrays: - Strings + - Example: ``Band.order([['name', 'asc'], ['year', 'desc']])`` - Symbols + - Example: ``Band.order([[:name, :asc]])`` - ``asc`` and ``desc`` methods on symbols + - Example: ``Band.order(:name.asc, :year.desc)`` - SQL syntax + - Example: ``Band.order('name desc')`` You can also use the ``asc()`` and ``desc()`` methods instead of using From f786188d24594194f934557ede1bdc76a4270afb Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 1 Nov 2024 10:18:49 -0400 Subject: [PATCH 048/113] MW PR fixes 1 --- .../includes/interact-data/modify-results.rb | 6 ++ source/interact-data/modify-results.txt | 78 +++++++++++-------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/source/includes/interact-data/modify-results.rb b/source/includes/interact-data/modify-results.rb index fb45192c..f2da3cfe 100644 --- a/source/includes/interact-data/modify-results.rb +++ b/source/includes/interact-data/modify-results.rb @@ -22,6 +22,12 @@ Band.limit(5) # end-limit +# start-skip-limit +Band.skip(2).limit(5) +# Skips the first two results and returns +# the following five results +# end-skip-limit + # start-skip Band.skip(3) diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt index 45fabed2..cc87951c 100644 --- a/source/interact-data/modify-results.txt +++ b/source/interact-data/modify-results.txt @@ -21,7 +21,7 @@ Overview -------- In this guide, you can learn how to customize the way that {+odm+} -returns results to you from queries. MongoDB allows you to perform the +returns results from queries. MongoDB allows you to perform the following actions to modify the way that results appear: - :ref:`mongoid-data-projection` @@ -36,17 +36,17 @@ Sample Data The examples in this guide use the ``Band`` model, which represents a band or musical group. The definition of the ``Band`` model might be different for each section to demonstrate different query -functionalities. Some sections might also use the ``Manager`` model, -which represents a person who manages a given band, or a ``Tour`` model, which -represents live performances by a certain band or musical group. +functionalities. Some sections also use the ``Manager`` model, +which represents a person who manages a given band, or the ``Tour`` +model, which represents live performances by a given band. .. _mongoid-data-projection: Return Specified Fields ----------------------- -In MongoDB, the process of specifying fields to include or exclude from -results is called *projection*. {+odm+} provides the following operators +In MongoDB, *projection* is the process of specifying fields to include +or exclude from results. {+odm+} provides the following operators to project fields: - ``only()``: Specifies fields to include @@ -98,7 +98,9 @@ Then, you can access the embedded fields from the returned documents: You can pass fields of referenced associations to the ``only()`` method, but the projection is ignored when loading the embedded objects. {+odm+} -loads all fields of the referenced associations. +loads all fields of the referenced associations. For example, when you +access the embedded ``Tour`` object as shown in the preceding code, +{+odm+} returns the complete object, not just the ``year`` field. .. note:: @@ -106,10 +108,10 @@ loads all fields of the referenced associations. you cannot specify an association and its fields in a projection in the same query. -If a document has ``has_one`` or ``has_and_belongs_to_many`` -associations, you must include the fields with foreign keys in the list -of attributes loaded when using ``only()`` for those associations to be -loaded. +If a document contains ``has_one`` or ``has_and_belongs_to_many`` +associations, and you want {+odm+} to load those associations when +you call the ``only()`` method, you must include the fields with foreign +keys in the list of attributes. In the following example, the ``Band`` and ``Manager`` models have a ``has_and_belongs_to_many`` association: @@ -163,14 +165,17 @@ objects: Sort Results ------------ -You can sort the order that {+odm+} returns documents by using the +You can specify the order in which {+odm+} returns documents by using the ``order()`` and ``order_by()`` methods. These methods accept a hash that indicates which fields to order the documents by, and whether to use an ascending or descending order for each field. -You can specify the sort direction in the following ways: +You can specify the sort direction by using integers, symbols, or +strings. We recommend using the same sorting syntax throughout your +application for consistency. The following list provides each syntax and +shows how to sort on the ``name`` and ``year`` fields: - Integers ``1`` (ascending) and ``-1`` (descending) @@ -178,7 +183,7 @@ You can specify the sort direction in the following ways: - Symbols ``:asc`` and ``:desc`` - - Example: ``Band.order(name: :asc)`` + - Example: ``Band.order(name: :asc, year: :desc)`` - Strings ``"asc"`` and ``"desc"`` @@ -194,7 +199,7 @@ The ``order()`` method also accepts the following sort specifications: - Symbols - - Example: ``Band.order([[:name, :asc]])`` + - Example: ``Band.order([[:name, :asc], [:year, :desc]])`` - ``asc`` and ``desc`` methods on symbols @@ -202,26 +207,28 @@ The ``order()`` method also accepts the following sort specifications: - SQL syntax - - Example: ``Band.order('name desc')`` + - Example: ``Band.order('name asc', 'year desc')`` -You can also use the ``asc()`` and ``desc()`` methods instead of using -``order()``: +.. tip:: -.. code-block:: ruby + Instead of using ``order()`` or ``order_by()``, you can also use the + ``asc()`` and ``desc()`` methods to specify sort orders: - Band.asc('name').desc('year') + .. code-block:: ruby + + Band.asc('name').desc('year') -If you chain sort specifications, the first call defines the most -significant criteria and the newest call defines the least significant -one. +When you chain sort specifications, the first call defines the first +sorting order and the newest call defines the last sorting order after +the previous sorts have been applied. .. TODO update link in the following note for scope .. note:: Sorting in Scopes If you define a scope on your model that includes a sort specification, - the scope sort takes precedence over the sort specified in a query, as the - default scope is evaluated first. + the scope sort takes precedence over the sort specified in a query, + because the default scope is evaluated first. .. _mongoid-data-skip-limit: @@ -229,14 +236,14 @@ Paginate Results ---------------- {+odm+} provides the ``limit()``, ``skip()``, and ``batch_size()`` -pagination operators that you can use on ``Criteria`` objects. The +pagination methods that you can use on ``Criteria`` objects. The following sections describe how to use these operators. Limit Number of Results ~~~~~~~~~~~~~~~~~~~~~~~ -You can limit the number of results that {+odm+} returns by using the -``limit()`` method. +You can use the ``limit()`` method to limit the number of results that +{+odm+} returns. The following code retrieves a maximum of ``5`` documents: @@ -250,12 +257,21 @@ Skip Results ~~~~~~~~~~~~ You can skip a specified number of results by using the ``skip()`` -method, or its alias ``offset()``. If you chain a ``limit()`` call, it -is applied after documents are skipped. +method, or its alias ``offset()``. + +If you chain a ``limit()`` call to ``skip()``, the limit is applied +after documents are skipped, as demonstrated in the following example: + +.. literalinclude:: /includes/interact-data/modify-results.rb + :start-after: start-skip-limit + :end-before: end-skip-limit + :language: ruby + :dedent: .. tip:: - When performing pagination, use ``skip()`` on :ref:`sorted results ` to ensure consistent results. + When performing pagination, use ``skip()`` on :ref:`sorted results ` + to ensure consistent results. The following code skips the first ``3`` documents when returning results: From 35c33cfa5240020a8c73d45379d0a4901e7a381f Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 1 Nov 2024 16:07:19 -0400 Subject: [PATCH 049/113] DOCSP-44954: scoping --- source/includes/interact-data/scoping.rb | 166 +++++++++++++++ source/interact-data/scoping.txt | 258 +++++++++++++++++++++++ source/interact-data/specify-query.txt | 5 + 3 files changed, 429 insertions(+) create mode 100644 source/includes/interact-data/scoping.rb create mode 100644 source/interact-data/scoping.txt diff --git a/source/includes/interact-data/scoping.rb b/source/includes/interact-data/scoping.rb new file mode 100644 index 00000000..7ad1b930 --- /dev/null +++ b/source/includes/interact-data/scoping.rb @@ -0,0 +1,166 @@ +# start-named-scope-1 +class Band + include Mongoid::Document + + field :country, type: String + field :genres, type: Array + + scope :japanese, ->{ where(country: "Japan") } + scope :rock, ->{ where(:genres.in => [ "rock" ]) } +end +# end-named-scope-1 + +# start-query-named-scope +Band.japanese.rock +# end-query-named-scope + +# start-named-scope-2 +class Band + include Mongoid::Document + + field :name, type: String + field :country, type: String + + scope :based_in, ->(country){ where(country: country) } +end +# end-named-scope-2 + +# start-query-named-scope-2 +Band.based_in("Spain") +# end-query-named-scope-2 + +# start-named-scope-3 +class Band + include Mongoid::Document + + def self.on_tour + true + end + + scope :on_tour, ->{ where(on_tour: true) } +end +# end-named-scope-3 + +# start-default-scope-1 +class Band + include Mongoid::Document + + field :name, type: String + field :active, type: Boolean + + default_scope ->{ where(active: true) } +end +# end-default-scope-1 + +# start-default-scope-2 +class Band + include Mongoid::Document + + field :name, type: String + field :on_tour, type: Boolean, default: true + + default_scope ->{ where(on_tour: false) } +end + +# Creates a new Band instance in which "on_tour" is "false" +Band.new +# end-default-scope-2 + +# start-unscoped +# Inline example +Band.unscoped.where(name: "Depeche Mode") + +# Block example +Band.unscoped do + Band.where(name: "Depeche Mode") +end +# end-unscoped + +# start-scope-association +class Label + include Mongoid::Document + + field :name, type: String + + embeds_many :bands +end + +class Band + include Mongoid::Document + + field :name, type: String + field :active, default: true + + embedded_in :label + default_scope ->{ where(active: true) } +end +# end-scope-association + +# start-scope-association-steps +label = Label.new(name: "Hello World Records") +band = Band.new(name: "Ghost Mountain") +label.bands.push(band) +label.bands # Displays the Band because "active" is "true" +band.update_attribute(:active, false) # Updates "active" to "false" +label.bands # Still displays the Band +label.reload.bands # Won't display the Band after reloading +# end-scope-association-steps + +# start-scope-query-behavior +class Band + include Mongoid::Document + + field :name + field :touring + field :member_count + + default_scope ->{ where(touring: true) } +end + +# Combines the condition to the default scope with "and" +Band.where(name: 'Infected Mushroom') +# Interpreted query: +# {"touring"=>true, "name"=>"Infected Mushroom"} + +# Combines the first condition to the default scope with "and" +Band.where(name: 'Infected Mushroom').or(member_count: 3) +# Interpreted query: +# {"$or"=>[{"touring"=>true, "name"=>"Infected Mushroom"}, {"member_count"=>3}]} + +# Combines the condition to the default scope with "or" +Band.or(member_count: 3) +# Interpreted query: +# {"$or"=>[{"touring"=>true}, {"member_count"=>3}]} +# end-scope-query-behavior + +# start-override-scope +class Band + include Mongoid::Document + + field :country, type: String + field :genres, type: Array + + scope :mexican, ->{ where(country: "Mexico") } +end +# end-override-scope + +# start-override-scope-block +Band.with_scope(Band.mexican) do + Band.all +end +# end-override-scope-block + +# start-class-methods +class Band + include Mongoid::Document + + field :name, type: String + field :touring, type: Boolean, default: true + + def self.touring + where(touring: true) + end +end + +Band.touring +# end-class-methods diff --git a/source/interact-data/scoping.txt b/source/interact-data/scoping.txt new file mode 100644 index 00000000..4e342995 --- /dev/null +++ b/source/interact-data/scoping.txt @@ -0,0 +1,258 @@ +.. _mongoid-data-scoping: + +======= +Scoping +======= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, crud, filter, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to implement **scopes** into your +{+odm+} models. Scopes provide a convenient way to reuse common filter +criteria. To learn more about creating filter criteria, see the +:ref:`mongoid-data-specify-query` guide. + +You might implement scopes into your application to reduce repeated code +if you are applying the same criteria to most queries. + +Named Scopes +------------ + +Named scopes are criteria defined at class load that are +referenced by a provided name. Similar to normal criteria, they are +lazily loaded and chainable. + +This example defines a ``Band`` model that includes the following named +scopes: + +- ``japanese``: Matches documents in which the value of the ``country`` + field is ``"Japan"`` + +- ``rock``: Matches documents in which the value of the ``genre`` + field includes ``"rock"`` + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-named-scope-1 + :end-before: end-named-scope-1 + :language: ruby + :dedent: + :emphasize-lines: 7-8 + +Then, you can query by using the named scopes, as shown in the following +code: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-query-named-scope + :end-before: end-query-named-scope + :language: ruby + :dedent: + +Advanced Scoping +~~~~~~~~~~~~~~~~ + +You can define ``Proc`` objects and blocks in named scopes so that they +accept parameters and extend functionality. + +This example defines a ``Band`` model that includes ``based_in`` scope, +which matches documents in which the ``country`` field value +is the specified value passed as a parameter: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-named-scope-2 + :end-before: end-named-scope-2 + :language: ruby + :emphasize-lines: 7 + :dedent: + +Then, you can query by using the ``based_in`` scope, as shown in the following +code: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-query-named-scope-2 + :end-before: end-query-named-scope-2 + :language: ruby + :dedent: + +{+odm+} allows you to define a scope that shadows an existing class +method, as shown in the following example: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-named-scope-3 + :end-before: end-named-scope-3 + :language: ruby + :dedent: + +You can direct {+odm+} to raise an error when a scope overwrites an +existing class method by setting the ``scope_overwrite_exception`` +configuration option to ``true``. + +.. TODO add link to config options page + +Default Scopes +-------------- + +Default scopes are useful for cases where you apply the same +criteria to most queries. By defining a default scope, you specify these +criteria as the default for any queries that use the model. Default +scopes return ``Criteria`` objects. + +The following code defines a default scope on the ``Band`` model to only +retrieve documents in which the ``active`` field value is ``true``: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-default-scope-1 + :end-before: end-default-scope-1 + :language: ruby + :dedent: + +Then, any queries on the ``Band`` model pre-filter for documents in which the +``active`` value is ``true``. + +Field Initialization +~~~~~~~~~~~~~~~~~~~~ + +Specifying a default scope initializes the fields of new models to +the values given in the default scope if those values are literals, such +as boolean values or integers. + +.. note:: Field and Scope Conflicts + + If you provide a default value in a field definition and in the + default scope, the value in the default scope takes precedence, as + shown in the following example: + + .. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-default-scope-2 + :end-before: end-default-scope-2 + :language: ruby + :dedent: + +You *should not* use dot notation to reference nested fields in default +scopes. This can lead to {+odm+} initializing unexpected fields in new +models. + +For example, if you define a default scope that references the +``tour.year`` field, a new model is initialized with the field +``tour.year`` instead of a ``tour`` field with a nested object that +contains a ``year`` field. + +When *querying*, {+odm+} interprets the dot notation correctly and matches +documents in which a nested field has the specified value. + +Associations +~~~~~~~~~~~~ + +If you define a default scope on a model that is part of an +association, you must reload the association to have scoping reapplied. +This is necessary for when you change a value of a document in the +association that would affect its visibility when the scope is applied. + +This example uses the following models: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-scope-association + :end-before: end-scope-association + :language: ruby + :dedent: + +Suppose you create a ``Label`` model that contains an association to a +``Band`` in which the value of ``active`` is ``true``. When you update +the ``active`` field to ``false``, {+odm+} still loads it despite the +default scope. To view the documents in the association with the scope +applied, you must call the ``reload()`` operator. + +The following code demonstrates this sequence: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-scope-association-steps + :end-before: end-scope-association-steps + :language: ruby + :dedent: + :emphasize-lines: 5, 7 + +or and nor Query Behavior +~~~~~~~~~~~~~~~~~~~~~~~~~ + +{+odm+} treats the criteria in a default scope the same way as any other +query conditions. This can lead to surprising behavior when using the +``or()`` and ``nor()`` methods. + +The following examples demonstrate how {+odm+} interprets queries on +models with a default scope: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-scope-query-behavior + :end-before: end-scope-query-behavior + :language: ruby + :dedent: + +To learn more about logical operations, see +:ref:`mongoid-query-logical-operations` in the Specify a Query guide. + +Disable Scope When Querying +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can direct {+odm+} to not apply the default scope by using the +``unscoped`` operator, as shown in the following examples: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-unscoped + :end-before: end-unscoped + :language: ruby + :dedent: + +Override Default Scope at Runtime +--------------------------------- + +You can use the ``with_scope()`` method to change the default scope in a +block at runtime. + +The following model defines the *named* scope ``mexican``: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-override-scope + :end-before: end-override-scope + :language: ruby + :dedent: + :emphasize-lines: 7 + +You can use the ``with_scope()`` method to set the ``mexican`` named +scope as the default scope at runtime, as shown in the following code: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-override-scope-block + :end-before: end-override-scope-block + :language: ruby + :dedent: + +Class Methods +------------- + +{+odm+} treats class methods on models that return ``Criteria`` objects +as scopes. You can chain these class methods when querying, as shown in +the following example: + +.. literalinclude:: /includes/interact-data/scoping.rb + :start-after: start-class-methods + :end-before: end-class-methods + :language: ruby + :dedent: + :emphasize-lines: 7-9, 12 + +Additional Information +---------------------- + +.. TODO add additional info \ No newline at end of file diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 63ed1b74..986b275a 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -11,6 +11,11 @@ Specify a Query .. meta:: :keywords: ruby framework, odm, crud, filter, code example +.. toctree:: + :caption: Queries + + /interact-data/scoping + .. contents:: On this page :local: :backlinks: none From 7d37c4dd21d93d3106cf5a1e7d794a89968e202c Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 1 Nov 2024 16:07:46 -0400 Subject: [PATCH 050/113] add landing page --- snooty.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/snooty.toml b/snooty.toml index 5ae4acc9..86c7bbcd 100644 --- a/snooty.toml +++ b/snooty.toml @@ -8,7 +8,8 @@ intersphinx = [ "https://www.mongodb.com/docs/manual/objects.inv", toc_landing_pages = [ "/quick-start-rails", "/quick-start-sinatra", - "/interact-data" + "/interact-data", + "/interact-data/specify-query" ] [constants] From cf1464ea7f3b04f10fd1137a70392e6e660eb713 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 1 Nov 2024 17:55:26 -0400 Subject: [PATCH 051/113] link --- source/interact-data/modify-results.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt index cc87951c..efdaadf9 100644 --- a/source/interact-data/modify-results.txt +++ b/source/interact-data/modify-results.txt @@ -222,13 +222,12 @@ When you chain sort specifications, the first call defines the first sorting order and the newest call defines the last sorting order after the previous sorts have been applied. -.. TODO update link in the following note for scope - .. note:: Sorting in Scopes - If you define a scope on your model that includes a sort specification, - the scope sort takes precedence over the sort specified in a query, - because the default scope is evaluated first. + If you define a :ref:`default scope ` on your + model that includes a sort specification, the scope sort takes precedence + over the sort specified in a query, because the default scope is + evaluated first. .. _mongoid-data-skip-limit: From 28e3a9d904b5b01442e0c42391ab0afc508ae7fb Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 1 Nov 2024 17:57:53 -0400 Subject: [PATCH 052/113] vale --- source/interact-data/scoping.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/interact-data/scoping.txt b/source/interact-data/scoping.txt index 4e342995..9be4fca4 100644 --- a/source/interact-data/scoping.txt +++ b/source/interact-data/scoping.txt @@ -31,9 +31,9 @@ if you are applying the same criteria to most queries. Named Scopes ------------ -Named scopes are criteria defined at class load that are -referenced by a provided name. Similar to normal criteria, they are -lazily loaded and chainable. +Named scopes are criteria defined at class load that are referenced by a +provided name. Similar to filter criteria, they are lazily loaded and +chainable. This example defines a ``Band`` model that includes the following named scopes: @@ -140,8 +140,8 @@ as boolean values or integers. :language: ruby :dedent: -You *should not* use dot notation to reference nested fields in default -scopes. This can lead to {+odm+} initializing unexpected fields in new +We do not recommend using dot notation to reference nested fields in default +scopes. This can direct {+odm+} to initialize unexpected fields in new models. For example, if you define a default scope that references the @@ -158,7 +158,7 @@ Associations If you define a default scope on a model that is part of an association, you must reload the association to have scoping reapplied. This is necessary for when you change a value of a document in the -association that would affect its visibility when the scope is applied. +association that affects its visibility when the scope is applied. This example uses the following models: @@ -255,4 +255,4 @@ the following example: Additional Information ---------------------- -.. TODO add additional info \ No newline at end of file +.. TODO add info links \ No newline at end of file From acfb65b6a7921563840e138258ed97c7c70c7cfc Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 1 Nov 2024 18:03:28 -0400 Subject: [PATCH 053/113] highlighting --- source/interact-data/scoping.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/interact-data/scoping.txt b/source/interact-data/scoping.txt index 9be4fca4..3733f521 100644 --- a/source/interact-data/scoping.txt +++ b/source/interact-data/scoping.txt @@ -117,6 +117,7 @@ retrieve documents in which the ``active`` field value is ``true``: :end-before: end-default-scope-1 :language: ruby :dedent: + :emphasize-lines: 7 Then, any queries on the ``Band`` model pre-filter for documents in which the ``active`` value is ``true``. @@ -139,6 +140,7 @@ as boolean values or integers. :end-before: end-default-scope-2 :language: ruby :dedent: + :emphasize-lines: 5, 7 We do not recommend using dot notation to reference nested fields in default scopes. This can direct {+odm+} to initialize unexpected fields in new From e213f8354aff1cc2eac9dd4e8db3ee6d7c87527b Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 4 Nov 2024 13:23:34 -0500 Subject: [PATCH 054/113] DOCSP-44821: specify a query pt 2 --- source/includes/interact-data/query.rb | 71 ++++++++++- source/interact-data/specify-query.txt | 158 +++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/source/includes/interact-data/query.rb b/source/includes/interact-data/query.rb index d2aa70b5..aaef73df 100644 --- a/source/includes/interact-data/query.rb +++ b/source/includes/interact-data/query.rb @@ -160,4 +160,73 @@ # {"year"=>{"$in"=>[1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960]}} # end-range-query - +# start-elem-match-1 +aerosmith = Band.create!(name: 'Aerosmith', tours: [ + {city: 'London', year: 1995}, + {city: 'New York', year: 1999}, +]) + +swans = Band.create!(name: 'Swans', tours: [ + {city: 'Milan', year: 2014}, + {city: 'Montreal', year: 2015}, +]) + +# Returns only "Aerosmith" +Band.elem_match(tours: {city: 'London'}) +# end-elem-match-1 + +# start-elemmatch-embedded-class +class Band + include Mongoid::Document + field :name, type: String + embeds_many :tours +end + +class Tour + include Mongoid::Document + field :city, type: String + field :year, type: Integer + embedded_in :band +end +# end-elemmatch-embedded-class + +# start-elemmatch-embedded-operations +aerosmith = Band.create!(name: 'Aerosmith') + +Tour.create!(band: aerosmith, city: 'London', year: 1995) +Tour.create!(band: aerosmith, city: 'New York', year: 1999) + +# Returns the "Aerosmith" document +Band.elem_match(tours: {city: 'London'}) +# end-elemmatch-embedded-operations + +# start-elemmatch-recursive +class Tag + include Mongoid::Document + + field name:, type: String + recursively_embeds_many +end + +# Creates the root Tag +root = Tag.create!(name: 'root') + +# Adds embedded Tags +sub1 = Tag.new(name: 'sub_tag_1', child_tags: [Tag.new(name: 'sub_sub_tag_1')]) + +root.child_tags << sub1 +root.child_tags << Tag.new(name: 'sub_tag_2') +root.save! + +# Searches for Tag in which one child Tag tame is "sub_tag_1" +Tag.elem_match(child_tags: {name: 'sub_tag_1'}) + +# Searches for a child Tag in which one child Tag tame is "sub_sub_tag_1" +root.child_tags.elem_match(child_tags: {name: 'sub_sub_tag_1'}) +# end-elemmatch-recursive + +# start-id-query-multiple +# Equivalent ways to match multiple documents +Band.find('5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e') +Band.find(['5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e']) +# end-id-query-multiple diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 63ed1b74..805fd46d 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -479,3 +479,161 @@ as the shown in the following example: Band.in(year: 1950) # Interpreted query: {"year"=>{"$in"=>[1950]}} + +Element Match +------------- + +You can use the ``elem_match()`` method to match documents that contain +an array field with at least one element that matches all the specified +query criteria. + +The following example creates a sample document that contains an array +field, then uses the ``elem_match()`` method to match documents in +which the ``tour`` array field contains an entry in which the value of +the ``city`` field is ``'London'``: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-elem-match-1 + :end-before: end-elem-match-1 + :language: ruby + :dedent: + +Associations +~~~~~~~~~~~~ + +You can use the ``elem_match()`` method to match embedded associations. + +This example uses the following models that define an embedded +association between ``Band`` and ``Tour``: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-elemmatch-embedded-class + :end-before: end-elemmatch-embedded-class + :language: ruby + :dedent: + +The following code creates a ``Band`` object and embedded ``Tour`` +objects, then uses the ``elem_match()`` method to query on the ``city`` +field: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-elemmatch-embedded-operations + :end-before: end-elemmatch-embedded-operations + :language: ruby + :dedent: + +.. note:: + + You cannot use ``elem_match()`` on non-embedded associations because + MongoDB does not perform a join on the collections. + If you perform this query, the conditions are added to the collection + that is the source of the non-embedded association rather than the + collection of the association. + +You can use ``elem_match()`` to query recursively embedded associations, +as shown in the following example: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-elemmatch-recursive + :end-before: end-elemmatch-recursive + :language: ruby + :dedent: + +Querying by _id Value +--------------------- + +{+odm+} provides the ``find()`` method that allows you to query +documents by their ``_id`` values. + +The following example uses the ``find()`` method to match a document +with the specified ``_id`` field value: + +.. code-block:: ruby + + Band.find('6725342d4cb3e161059f91d7') + +.. note:: Type Conversion + + The ``find()`` method performs the type conversion of the argument that + you pass to the type declared for the ``_id`` field in the model. By + default, the ``_id`` field is defined as a ``BSON::ObjectId`` type. + + The preceding example is equivalent to the following code, which + passes an ``BSON::ObjectId`` instance as the argument to ``find()``: + + .. code-block:: ruby + + Band.find(BSON::ObjectId.from_string('5f0e41d92c97a64a26aabd10')) + + If you use the {+ruby-driver+} to query on the ``_id`` field, + ``find()`` does not internally perform the type conversion. + +The ``find()`` method accepts multiple arguments, or an array of arguments. +{+odm+} interprets each argument or array element as an +``_id`` value, and returns documents with all of the specified ``_id`` +values in an array, as shown in the following example: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-id-query-multiple + :end-before: end-id-query-multiple + :language: ruby + :dedent: + +The ``find()`` method exhibits the following behavior: + +- If you provide the same ``_id`` value more than once, {+odm+} + returns only one document, if one exists. + +- {+odm+} does not return documents in an ordered way. Documents might + be returned in different order from the order of the provided ``_id`` + values. + +- If any of the ``_id`` values are not found in the database, the result + depends on the value of the ``raise_not_found_error`` configuration + option. + + If you set the option to ``true``, ``find()`` raises a + ``Mongoid::Errors::DocumentNotFound`` error if any of the ``_id`` + values are not found. + + If you set the option to ``false`` and query for a single ``_id`` + value, ``find()`` returns ``nil`` if {+odm+} does not match any + document. If you pass multiple ``_id`` values and + some or all are not matched, the return value is an array of any documents + that match, or an empty array if no documents match. + +Additional Query Methods +------------------------ + +This section describes more query methods that you can use in {+odm+}. + +Count Documents +~~~~~~~~~~~~~~~ + +You can use the ``count()`` and ``estimated_count()`` methods to count +the number of documents in a collection. + +You can count the number of documents that match filter criteria by +using the ``count()`` method: + +.. code-block:: ruby + + # Counts all documents in collection + Band.count + + # Counts documents that match criteria + Band.where(name: 'Joywave').count + +You can get an approximate number of documents in the collection from +the collection metadata by using the ``estimated_count()`` method: + +.. code-block:: ruby + + Band.estimated_count + +.. note:: + + The ``estimated_count()`` method does not accept query conditions, + including conditions set by a :ref:`scope <>` on the model. + + From 78f72842cdd8d4fee3c944e8c8e60d50a1d267f0 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 4 Nov 2024 13:54:23 -0500 Subject: [PATCH 055/113] MR PR fixes 1 --- source/includes/interact-data/scoping.rb | 10 +++++++--- source/interact-data/scoping.txt | 19 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/source/includes/interact-data/scoping.rb b/source/includes/interact-data/scoping.rb index 7ad1b930..2e1889a7 100644 --- a/source/includes/interact-data/scoping.rb +++ b/source/includes/interact-data/scoping.rb @@ -48,7 +48,7 @@ class Band field :name, type: String field :active, type: Boolean - default_scope ->{ where(active: true) } + default_scope -> { where(active: true) } end # end-default-scope-1 @@ -102,8 +102,12 @@ class Band label.bands.push(band) label.bands # Displays the Band because "active" is "true" band.update_attribute(:active, false) # Updates "active" to "false" -label.bands # Still displays the Band -label.reload.bands # Won't display the Band after reloading + +# Displays the "Ghost Mountain" band +label.bands # => {"_id":"...","name":"Ghost Mountain",...} + +# Won't display "Ghost Mountain" band after reloading +label.reload.bands # => nil # end-scope-association-steps # start-scope-query-behavior diff --git a/source/interact-data/scoping.txt b/source/interact-data/scoping.txt index 3733f521..49329818 100644 --- a/source/interact-data/scoping.txt +++ b/source/interact-data/scoping.txt @@ -51,8 +51,10 @@ scopes: :dedent: :emphasize-lines: 7-8 -Then, you can query by using the named scopes, as shown in the following -code: +Then, you can query by using the named scopes. The following query uses +the named scopes to match documents in which value of the ``country`` +field is ``"Japan"`` and value of the ``genre`` field includes +``"rock"``: .. literalinclude:: /includes/interact-data/scoping.rb :start-after: start-query-named-scope @@ -66,7 +68,7 @@ Advanced Scoping You can define ``Proc`` objects and blocks in named scopes so that they accept parameters and extend functionality. -This example defines a ``Band`` model that includes ``based_in`` scope, +This example defines a ``Band`` model that includes the ``based_in`` scope, which matches documents in which the ``country`` field value is the specified value passed as a parameter: @@ -109,8 +111,11 @@ criteria to most queries. By defining a default scope, you specify these criteria as the default for any queries that use the model. Default scopes return ``Criteria`` objects. -The following code defines a default scope on the ``Band`` model to only -retrieve documents in which the ``active`` field value is ``true``: +To create a default scope, you must define the ``default_scope()`` method +on your model class. + +The following code defines the ``default_scope()`` method on the ``Band`` +model to only retrieve documents in which the ``active`` field value is ``true``: .. literalinclude:: /includes/interact-data/scoping.rb :start-after: start-default-scope-1 @@ -243,8 +248,8 @@ scope as the default scope at runtime, as shown in the following code: Class Methods ------------- -{+odm+} treats class methods on models that return ``Criteria`` objects -as scopes. You can chain these class methods when querying, as shown in +{+odm+} treats class methods that return ``Criteria`` objects +as scopes. You can query by using these class methods, as shown in the following example: .. literalinclude:: /includes/interact-data/scoping.rb From 22de652b01b1d729014b84c5e93712e56056f221 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 4 Nov 2024 15:02:10 -0500 Subject: [PATCH 056/113] wip --- source/includes/interact-data/query.rb | 17 +++++++ source/interact-data/specify-query.txt | 66 ++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/source/includes/interact-data/query.rb b/source/includes/interact-data/query.rb index aaef73df..9bde7bf2 100644 --- a/source/includes/interact-data/query.rb +++ b/source/includes/interact-data/query.rb @@ -230,3 +230,20 @@ class Tag Band.find('5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e') Band.find(['5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e']) # end-id-query-multiple + +# start-ordinal-examples +# Returns the first document in the collection +Band.first + +# Returns the first matching document +Band.where(founded: {'$gt' => 1980}).first + +# Returns the first two matching documents +Band.first(2) + +# Returns the last matching document +Band.where(founded: {'$gt' => 1980}).last + +# Returns the second to last document +Band.second_to_last +# end-ordinal-examples \ No newline at end of file diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 6b07c4d5..63c8a023 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -627,7 +627,13 @@ using the ``count()`` method: Band.count # Counts documents that match criteria - Band.where(name: 'Joywave').count + Band.where(country: 'England').count + +.. tip:: length() and size() Methods + + You can also use the ``length()`` or ``size()`` method to count documents. + These methods cache subsequent calls to the database, which might + produce performance improvements. You can get an approximate number of documents in the collection from the collection metadata by using the ``estimated_count()`` method: @@ -636,9 +642,61 @@ the collection metadata by using the ``estimated_count()`` method: Band.estimated_count -.. note:: +The ``estimated_count()`` method does not accept query conditions, +including conditions set by a :ref:`scope ` on +the model. If you are calling this method on a model that has a +default scope, you must first call the ``unscoped()`` method to +disable the scope. + +Ordinal Methods +~~~~~~~~~~~~~~~ + +The methods described in the following list allow you to select a specific +result from the list of returned documents based on its position. + +- ``first()``: Returns the first matching document. You can get the + first ``n`` documents by passing an integer-valued parameter. This method + automatically uses a sort on the ``_id`` field. **See lines 1-8 in the + following code for usage examples** + +- ``last()``: Returns the last matching document. You can get the + last ``n`` documents by passing an integer-valued parameter. This method + automatically uses a sort on the ``_id`` field. **See lines in the + following code for usage examples** + +- ``first_or_create()``: Returns the first matching document. If no + document matches, creates and returns a newly persisted one. + +- ``first_or_initialize()``: Returns the first matching document. If no + document matches, returns a new one. - The ``estimated_count()`` method does not accept query conditions, - including conditions set by a :ref:`scope <>` on the model. +- ``second()``: Returns the second matching document. This method + automatically uses a sort on the ``_id`` field. +- ``second()``: Returns the second matching document. This method + automatically uses a sort on the ``_id`` field. +- ``third()``: Returns the third matching document. This method + automatically uses a sort on the ``_id`` field. + +- ``fourth()``: Returns the fourth matching document. This method + automatically uses a sort on the ``_id`` field. + +- ``fifth()``: Returns the fifth matching document. This method + automatically uses a sort on the ``_id`` field. + +- ``second_to_last()``: Returns the second to last matching document. + This method automatically uses a sort on the ``_id`` field. + +- ``third_to_last()``: Returns the third to last matching document. + This method automatically uses a sort on the ``_id`` field. + +The following code demonstrates how to use some of the methods described +in the preceding list: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-ordinal-examples + :end-before: end-ordinal-examples + :language: ruby + :dedent: + :linenos: From 65d1783354b2bc0d1d3827473911865ee83fa18b Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 4 Nov 2024 15:13:50 -0500 Subject: [PATCH 057/113] wip --- source/interact-data/specify-query.txt | 49 ++++++++++++++++---------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 63c8a023..74b7e6a7 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -575,7 +575,7 @@ with the specified ``_id`` field value: The ``find()`` method accepts multiple arguments, or an array of arguments. {+odm+} interprets each argument or array element as an -``_id`` value, and returns documents with all of the specified ``_id`` +``_id`` value, and returns documents with all the specified ``_id`` values in an array, as shown in the following example: .. literalinclude:: /includes/interact-data/query.rb @@ -656,13 +656,13 @@ result from the list of returned documents based on its position. - ``first()``: Returns the first matching document. You can get the first ``n`` documents by passing an integer-valued parameter. This method - automatically uses a sort on the ``_id`` field. **See lines 1-8 in the - following code for usage examples** + automatically uses a sort on the ``_id`` field. *See lines 1-8 in the + following code for examples* - ``last()``: Returns the last matching document. You can get the last ``n`` documents by passing an integer-valued parameter. This method - automatically uses a sort on the ``_id`` field. **See lines in the - following code for usage examples** + automatically uses a sort on the ``_id`` field. *See line 11 in the + following code an example* - ``first_or_create()``: Returns the first matching document. If no document matches, creates and returns a newly persisted one. @@ -670,28 +670,29 @@ result from the list of returned documents based on its position. - ``first_or_initialize()``: Returns the first matching document. If no document matches, returns a new one. -- ``second()``: Returns the second matching document. This method - automatically uses a sort on the ``_id`` field. +- ``second()``: Returns the second matching document. Automatically uses + a sort on the ``_id`` field. -- ``second()``: Returns the second matching document. This method - automatically uses a sort on the ``_id`` field. +- ``second()``: Returns the second matching document. Automatically uses + a sort on the ``_id`` field. -- ``third()``: Returns the third matching document. This method - automatically uses a sort on the ``_id`` field. +- ``third()``: Returns the third matching document. Automatically uses + a sort on the ``_id`` field. -- ``fourth()``: Returns the fourth matching document. This method - automatically uses a sort on the ``_id`` field. +- ``fourth()``: Returns the fourth matching document. Automatically uses + a sort on the ``_id`` field. -- ``fifth()``: Returns the fifth matching document. This method - automatically uses a sort on the ``_id`` field. +- ``fifth()``: Returns the fifth matching document. Automatically uses + a sort on the ``_id`` field. - ``second_to_last()``: Returns the second to last matching document. - This method automatically uses a sort on the ``_id`` field. + Automatically usesa sort on the ``_id`` field. *See line 14 in the + following code an example* - ``third_to_last()``: Returns the third to last matching document. - This method automatically uses a sort on the ``_id`` field. + Automatically uses a sort on the ``_id`` field. -The following code demonstrates how to use some of the methods described +The following code demonstrates how to use some methods described in the preceding list: .. literalinclude:: /includes/interact-data/query.rb @@ -700,3 +701,15 @@ in the preceding list: :language: ruby :dedent: :linenos: + +.. tip:: Error Generation + + Each method described in this section has a ``-!`` suffixed counterpart + that returns an error if {+odm+} doesn't match any documents. For + example, to implement error handling in your application when your + query returns no results, use the ``first!()`` method instead of + ``first()``. + +List Field Values +~~~~~~~~~~~~~~~~~ + From 889522492af7a0a9da52e97cb611d118c3a715ee Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 5 Nov 2024 13:49:04 -0500 Subject: [PATCH 058/113] wip --- source/includes/interact-data/query.rb | 94 ++++++++++- source/interact-data/modify-results.txt | 5 + source/interact-data/specify-query.txt | 210 +++++++++++++++++++++++- source/reference/fields.txt | 3 +- 4 files changed, 301 insertions(+), 11 deletions(-) diff --git a/source/includes/interact-data/query.rb b/source/includes/interact-data/query.rb index 9bde7bf2..16e6277d 100644 --- a/source/includes/interact-data/query.rb +++ b/source/includes/interact-data/query.rb @@ -246,4 +246,96 @@ class Tag # Returns the second to last document Band.second_to_last -# end-ordinal-examples \ No newline at end of file +# end-ordinal-examples + +# start-field-val-examples +Band.distinct(:name) +# Example output: "Ghost Mountain" "Hello Goodbye" "She Said" + +Band.where(:members.gt => 2).distinct(:name) +# Example output: "Arctic Monkeys" "The Smiths" + +Band.distinct('tours.city') +# Example output: "London" "Sydney" "Amsterdam" + +Band.all.pick(:name) +# Example output: "The Smiths" + +Band.all.pluck(:country) +# Example output: "England" "Spain" "England" "Japan" + +Band.all.tally(:country) +# Example output: ["England",2] ["Italy",3] +# end-field-val-examples + +# start-query-findby +# Simple equality query +Band.find_by(name: "Photek") + +# Performs an action on each returned result +Band.find_by(name: "Tool") do |band| + band.fans += 1 +end +# end-query-findby + +# start-query-find-or-create +# If no matches, creates a Band with just the "name" field +Band.find_or_create_by(name: "Photek") + +# If no matches, creates a Band with just the "name" field because the +# query condition is not a literal +Band.where(:likes.gt => 10).find_or_create_by(name: "Photek") + +# Creates a Band in which the name is Aerosmith because there is no +# document in which "name" is Photek and Aerosmith at the same time +Band.where(name: "Photek").find_or_create_by(name: "Aerosmith") +# end-query-find-or-create + +# start-regex +# Matches "description" values that start exactly with "Impala" +Band.where(description: /\AImpala/) +# => nil + +# Matches "description" values that start exactly with "Impala" +Band.where(description: BSON::Regexp::Raw.new('^Impala')) +# => nil + +# Matches "description" values that start exactly with "Impala" with +# the multiline option +Band.where(description: BSON::Regexp::Raw.new('^Impala', 'm')) +# => Returns sample document +# end-regex + +# start-field-conversion-model +class Album + include Mongoid::Document + + field :release_date, type: Date + field :last_commented, type: Time + field :last_purchased +end +# end-field-conversion-model + +# start-date-queries-1 +Album.where(release_date: Date.today) +# Interpreted query: +# {"release_date"=>2024-11-05 00:00:00 UTC} + +Album.where(last_commented: Time.now) +# Interpreted query: +# {"last_commented"=>2024-11-04 17:20:47.329472 UTC} +# end-date-queries-1 + +# start-date-queries-2 +Album.where(last_commented: Date.today) +# Interpreted query: +# {"last_commented"=>Mon, 04 Nov 2024 00:00:00.000000000 EST -05:00} + +Album.where(last_purchased: Date.today) +# Interpreted query: +# {"last_purchased"=>"2024-11-04"} + +Album.where(last_reviewed: Date.today) +# Interpreted query: +# {"last_reviewed"=>2024-11-04 00:00:00 UTC} +# end-date-queries-2 diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt index efdaadf9..11c57714 100644 --- a/source/interact-data/modify-results.txt +++ b/source/interact-data/modify-results.txt @@ -252,6 +252,11 @@ The following code retrieves a maximum of ``5`` documents: :language: ruby :dedent: +.. note:: + + Alternatively, you can use the ``take()`` method to retrieve a + specified number of documents from the database. + Skip Results ~~~~~~~~~~~~ diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 74b7e6a7..eda10f2e 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -43,9 +43,8 @@ Sample Data The examples in this guide use the ``Band`` model, which represents a band or musical group. The definition of the ``Band`` model might be different for each section to demonstrate different query -functionalities. Some sections might also use the ``Manager`` model, -which represents a person who manages a given band, or a ``Show`` model, which -represents a live performance by a certain band or musical group. +functionalities. Some sections might also use other supplementary models +to demonstrate query functionality. Queries in {+odm+} ------------------ @@ -607,6 +606,128 @@ The ``find()`` method exhibits the following behavior: some or all are not matched, the return value is an array of any documents that match, or an empty array if no documents match. +find() Variations +----------------- + +This section describes methods that are similar to the ``find()`` method +described in the preceding section. + +You can use the ``find_by()`` method to retrieve documents based on the +provided criteria. If no documents are found, it raises an error or +returns ``nil`` depending on how you set the ``raise_not_found_error`` +configuration option. + +The following code demonstrates how to use the ``find_by()`` method: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-query-findby + :end-before: end-query-findby + :language: ruby + :dedent: + +You can use the ``find_or_create_by()`` method to retrieve documents +based on the provided criteria. If no documents are found, it creates +and returns a newly persisted one. + +The following code demonstrates how to use the ``find_or_create_by()`` +method: + +The following code demonstrates how to use the ``find_by()`` method: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-query-find-or-create + :end-before: end-query-find-or-create + :language: ruby + :dedent: + +You can use the ``find_or_initialize_by()`` method to to retrieve documents +based on the provided criteria. If no documents are found, it returns a +new one, without persisting it. The syntax for +``find_or_initialize_by()`` is the same as in the preceding example. + +Regular Expressions +------------------- + +{+odm+} allows you to query documents by using regular expressions in +your filter criteria. + +The following code creates a sample ``Band`` model: + +.. code-block:: ruby + + Band.create!(name: 'Tame Impala', description: "Tame\nImpala is an American band") + +You can perform queries by using {+language+} regular expressions, as +shown in the following code: + +.. code-block:: ruby + + # Matches documents in which the "name" field includes the string "impala" + Band.where(name: /impala/i) + # => Returns sample document + +You can also perform queries by using PCRE syntax and +``BSON::Regexp::Raw`` objects: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-regex + :end-before: end-regex + :language: ruby + :dedent: + +Field Type Query Conversions +---------------------------- + +When you specify a query on a field defined in a model, {+odm+} converts +the query value based on how the field is defined, and if it has a +specified type. + +Consider the following ``Album`` model definition that +contains a ``Date``-valued field, a ``Time``-valued field and an +implicit ``Object``-valued field. The model also intentionally *does not define* +a field named ``last_reviewed``: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-field-conversion-model + :end-before: end-field-conversion-model + :language: ruby + :dedent: + +You can query on the ``release_date`` and ``last_commented`` fields +by using ``Date`` and ``Time`` values, as shown in the following code: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-date-queries-1 + :end-before: end-date-queries-1 + :language: ruby + :dedent: + +However, if you query by using only ``Date`` values on fields defined +as other types, the generated queries display the default conversion +behavior, as shown in the following example: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-date-queries-2 + :end-before: end-date-queries-2 + :language: ruby + :dedent: + +In the preceding example, the following conversions apply: + +.. TODO add link to time zone configuration + +- When using a ``Date`` value to query the ``Time``-valued + ``last_commented`` field, {+odm+} interprets the date to be in local + time and applies the :ref:`configured time zone <>`. + +- When querying on the ``last_purchased`` field that has no explicit + type, the date is used unmodified in the constructed query. + +- When querying on the undefined ``last_reviewed`` field, {+odm+} + interprets the ``Date`` to be in UTC and converts to a time, matching + the behavior of querying a ``Date``-valued field such as + ``release_date``. + Additional Query Methods ------------------------ @@ -657,12 +778,12 @@ result from the list of returned documents based on its position. - ``first()``: Returns the first matching document. You can get the first ``n`` documents by passing an integer-valued parameter. This method automatically uses a sort on the ``_id`` field. *See lines 1-8 in the - following code for examples* + following code for examples.* - ``last()``: Returns the last matching document. You can get the last ``n`` documents by passing an integer-valued parameter. This method automatically uses a sort on the ``_id`` field. *See line 11 in the - following code an example* + following code for an example.* - ``first_or_create()``: Returns the first matching document. If no document matches, creates and returns a newly persisted one. @@ -687,7 +808,7 @@ result from the list of returned documents based on its position. - ``second_to_last()``: Returns the second to last matching document. Automatically usesa sort on the ``_id`` field. *See line 14 in the - following code an example* + following code for an example.* - ``third_to_last()``: Returns the third to last matching document. Automatically uses a sort on the ``_id`` field. @@ -710,6 +831,79 @@ in the preceding list: query returns no results, use the ``first!()`` method instead of ``first()``. -List Field Values -~~~~~~~~~~~~~~~~~ +Survey Field Values +~~~~~~~~~~~~~~~~~~~ + +To inspect the values of specified fields of documents in a +collection, you can use the following methods: + +- ``distinct()``: Gets a list of distinct values for a single field. + *See lines 1-7 in the following code for examples.* + +- ``pick()``: Gets the values from one document for the provided fields. + Returns ``nil`` for unset fields and for non-existent fields. + *See line 10 in the following code for an example.* + +- ``pluck()``: Gets all values for the provided field. Returns ``nil`` + for unset fields and for non-existent fields. + *See line 13 in the following code for an example.* + +- ``tally()``: Gets a mapping of values to counts for the specified + field. *See line 16 in the following code for an example.* + +The preceding methods accept field names referenced by using dot +notation, which allows you to reference fields in embedded associations. +They also respect :ref:`field aliases `, including +those defined in embedded documents. + +The following code demonstrates how to use these methods: + +.. literalinclude:: /includes/interact-data/query.rb + :start-after: start-field-val-examples + :end-before: end-field-val-examples + :language: ruby + :dedent: + :linenos: + +Miscellaneous +~~~~~~~~~~~~~ + +The following list describes {+odm+} methods that do not fit into another +category: + +- ``each()``: Iterates over all matching documents. + +.. code-block:: ruby + + # Print each matching document "name" to console + Band.where(:members.gt => 1).each do |band| + p band.name + end + +- ``exists?()``: Determines if any matching documents exist, returning + ``true`` if at least one matching document is found. + +.. code-block:: ruby + + # Checks existence of any document + Band.exists? + + # Checks existence based on query + Band.where(name: "Le Tigre").exists? + Band.exists?(name: "Le Tigre") + + # Checks existence based on "_id" value + Band.exists?('6320d96a3282a48cfce9e72c') + + # Always returns false + Band.exists?(false) + Band.exists?(nil) + +Additional Information +---------------------- + +To learn how to modify how {+odm+} returns results to you, see +:ref:`mongoid-data-modify-results`. +To learn more about defining scopes on your models, see +:ref:`source/interact-data/scoping.txt`. \ No newline at end of file diff --git a/source/reference/fields.txt b/source/reference/fields.txt index e53ce131..6198bb6c 100644 --- a/source/reference/fields.txt +++ b/source/reference/fields.txt @@ -717,8 +717,7 @@ and criteria while performing the conversion for you. criteria = Band.where(name: "Placebo") criteria.selector # { "n" => "Placebo" } - -.. _field-aliases: +.. _mongoid-field-aliases: Field Aliases ------------- From 4de789e37c198da5633e2ea3a5073f525b0a8c41 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 5 Nov 2024 13:53:05 -0500 Subject: [PATCH 059/113] small fixes --- source/interact-data/specify-query.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index eda10f2e..a188a660 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -632,8 +632,6 @@ and returns a newly persisted one. The following code demonstrates how to use the ``find_or_create_by()`` method: -The following code demonstrates how to use the ``find_by()`` method: - .. literalinclude:: /includes/interact-data/query.rb :start-after: start-query-find-or-create :end-before: end-query-find-or-create @@ -906,4 +904,4 @@ To learn how to modify how {+odm+} returns results to you, see :ref:`mongoid-data-modify-results`. To learn more about defining scopes on your models, see -:ref:`source/interact-data/scoping.txt`. \ No newline at end of file +:ref:`mongoid-data-scoping`. \ No newline at end of file From 27873d2e2dea8ad4de6b4731cd6b6bf716a95b22 Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Fri, 8 Nov 2024 06:52:38 -0800 Subject: [PATCH 060/113] DOCSP-42767 Aggregation (#57) --- snooty.toml | 10 +- source/aggregation.txt | 232 ++++++++++++++++++ source/includes/aggregation/builder-dsl.rb | 6 + .../includes/aggregation/ruby-aggregation.rb | 21 ++ source/reference/aggregation.txt | 203 --------------- source/reference/associations.txt | 2 +- source/reference/map-reduce.txt | 2 +- source/working-with-data.txt | 4 +- 8 files changed, 269 insertions(+), 211 deletions(-) create mode 100644 source/aggregation.txt create mode 100644 source/includes/aggregation/builder-dsl.rb create mode 100644 source/includes/aggregation/ruby-aggregation.rb delete mode 100644 source/reference/aggregation.txt diff --git a/snooty.toml b/snooty.toml index df700929..5b4584c2 100644 --- a/snooty.toml +++ b/snooty.toml @@ -1,9 +1,10 @@ name = "mongoid" title = "Mongoid" -intersphinx = [ "https://www.mongodb.com/docs/manual/objects.inv", - "https://www.mongodb.com/docs/atlas/objects.inv" - ] +intersphinx = [ + "https://www.mongodb.com/docs/manual/objects.inv", + "https://www.mongodb.com/docs/atlas/objects.inv", +] toc_landing_pages = [ "/quick-start-rails", @@ -24,4 +25,5 @@ quickstart-sinatra-app-name = "my-sinatra-app" quickstart-rails-app-name = "my-rails-app" feedback-widget-title = "Feedback" server-manual = "Server manual" -api = "https://www.mongodb.com/docs/mongoid/master/api" \ No newline at end of file +api-root = "https://www.mongodb.com/docs/mongoid/master/api/Mongoid" +api = "https://www.mongodb.com/docs/mongoid/master/api" diff --git a/source/aggregation.txt b/source/aggregation.txt new file mode 100644 index 00000000..55fc3a16 --- /dev/null +++ b/source/aggregation.txt @@ -0,0 +1,232 @@ +.. _mongoid-aggregation: + +==================================== +Transform Your Data with Aggregation +==================================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, transform, pipeline + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use {+odm+} to perform **aggregation +operations**. + +Aggregation operations process data in your MongoDB collections and return +computed results. The MongoDB Aggregation framework, which is part of the Query +API, is modeled on the concept of data processing pipelines. Documents enter a +pipeline that contains one or more stages, and this pipeline transforms the +documents into an aggregated result. + +Aggregation operations function similarly to car factories with assembly +lines. The assembly lines have stations with specialized tools to +perform specific tasks. For example, when building a car, the assembly +line begins with the frame. Then, as the car frame moves through the +assembly line, each station assembles a separate part. The result is a +transformed final product, the finished car. + +The assembly line represents the *aggregation pipeline*, the individual +stations represent the *aggregation stages*, the specialized tools +represent the *expression operators*, and the finished product +represents the *aggregated result*. + +Compare Aggregation and Find Operations +--------------------------------------- + +The following table lists the different tasks you can perform with find +operations, compared to what you can achieve with aggregation +operations. The aggregation framework provides expanded functionality +that allows you to transform and manipulate your data. + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Find Operations + - Aggregation Operations + + * - | Select *certain* documents to return + | Select *which* fields to return + | Sort the results + | Limit the results + | Count the results + - | Select *certain* documents to return + | Select *which* fields to return + | Sort the results + | Limit the results + | Count the results + | Rename fields + | Compute new fields + | Summarize data + | Connect and merge data sets + +{+odm+} Builders +---------------- + +You can construct an aggregation pipeline by using {+odm+}'s high-level +domain-specific language (DSL). The DSL supports the following aggregation +pipeline operators: + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Operator + - Method Name + + * - :manual:`$group ` + - ``group()`` + + * - :manual:`$project ` + - ``project()`` + + * - :manual:`$unwind ` + - ``unwind()`` + +To create an aggregation pipeline by using one of the preceding operators, call +the corresponding method on an instance of ``Criteria``. Calling the method adds +the aggregation operation to the ``pipeline`` atrritbure of the ``Criteria`` +instance. To run the aggregation pipeline, pass the ``pipeline`` attribute value +to the ``Collection#aggregate()`` method. + +Example +~~~~~~~ + +Consider a database that contains a collection with documents that are modeled by +the following classes: + +.. code-block:: ruby + + class Tour + include Mongoid::Document + + embeds_many :participants + + field :name, type: String + field :states, type: Array + end + + class Participant + include Mongoid::Document + + embedded_in :tour + + field :name, type: String + end + +In this example, the ``Tour`` model represents the name of a tour and the states +it travels through, and the ``Participant`` model represents the name of a +person participating in the tour. + +The following example creates an aggregation pipeline that outputs the states a +participant has visited by using the following +aggregation operations: + +- ``match()``, which find documents in which the ``participants.name`` field + value is ``"Serenity"`` +- ``unwind()``, which deconstructs the ``states`` array field and outputs a + document for each element in the array +- ``group()``, which groups the documents by the value of their ``states`` field +- ``project()``, which prompts the pipeline to return only the ``_id`` and + ``states`` fields + +.. io-code-block:: + + .. input:: /includes/aggregation/builder-dsl.rb + :language: ruby + + .. output:: + + [{"states":["OR","WA","CA"]}] + +Aggregation without Builders +---------------------------- + +You can use the ``Collection#aggregate()`` method to run aggregation operations that do not have +corresponding builder methods by passing in an array of aggregation +operations. Using this method to perform the aggregation returns +raw ``BSON::Document`` objects rather than ``Mongoid::Document`` model +instances. + +Example +~~~~~~~ + +Consider a database that contains a collection with documents that are modeled +by the following classes: + +.. code-block:: ruby + + class Band + include Mongoid::Document + has_many :tours + has_many :awards + field :name, type: String + end + + class Tour + include Mongoid::Document + belongs_to :band + field :year, type: Integer + end + + class Award + include Mongoid::Document + belongs_to :band + field :name, type: String + end + +The following example creates an aggregation pipeline to retrieve all bands that +have toured since ``2000`` and have at least ``1`` award: + +.. io-code-block:: + + .. input:: /includes/aggregation/ruby-aggregation.rb + :language: ruby + + .. output:: + + [ + {"_id": "...", "name": "Deftones" }, + {"_id": "...", "name": "Tool"}, + ... + ] + +.. tip:: + + The preceding example projects only the ``_id`` field of the output + documents. It then uses the projected results to find the documents and return + them as ``Mongoid::Document`` model instances. This optional step is not + required to run an aggregation pipeline. + +Additional Information +---------------------- + +To view a full list of aggregation operators, see :manual:`Aggregation +Operators. ` + +To learn about assembling an aggregation pipeline and view examples, see +:manual:`Aggregation Pipeline. ` + +To learn more about creating pipeline stages, see :manual:`Aggregation +Stages. ` + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about any of the methods discussed in this +guide, see the following API documentation: + +- `group() <{+api-root+}/Criteria/Queryable/Aggregable.html#group-instance_method>`__ +- `project() <{+api-root+}/Criteria/Queryable/Aggregable.html#project-instance_method>`__ +- `unwind() <{+api-root+}/Criteria/Queryable/Aggregable.html#unwind-instance_method>`__ \ No newline at end of file diff --git a/source/includes/aggregation/builder-dsl.rb b/source/includes/aggregation/builder-dsl.rb new file mode 100644 index 00000000..9e884ba7 --- /dev/null +++ b/source/includes/aggregation/builder-dsl.rb @@ -0,0 +1,6 @@ +criteria = Tour.where('participant.name' => 'Serenity'). + unwind(:states). + group(_id: 'states', :states.add_to_set => '$states'). + project(_id: 0, states: 1) + +@states = Tour.collection.aggregate(criteria.pipeline).to_json \ No newline at end of file diff --git a/source/includes/aggregation/ruby-aggregation.rb b/source/includes/aggregation/ruby-aggregation.rb new file mode 100644 index 00000000..91181f90 --- /dev/null +++ b/source/includes/aggregation/ruby-aggregation.rb @@ -0,0 +1,21 @@ +band_ids = Band.collection.aggregate([ + { '$lookup' => { + from: 'tours', + localField: '_id', + foreignField: 'band_id', + as: 'tours', + } }, + { '$lookup' => { + from: 'awards', + localField: '_id', + foreignField: 'band_id', + as: 'awards', + } }, + { '$match' => { + 'tours.year' => {'$gte' => 2000}, + 'awards._id' => {'$exists' => true}, + } }, + {'$project' => {_id: 1}}, +]) + +bands = Band.find(band_ids.to_a) \ No newline at end of file diff --git a/source/reference/aggregation.txt b/source/reference/aggregation.txt deleted file mode 100644 index 5a5c83d9..00000000 --- a/source/reference/aggregation.txt +++ /dev/null @@ -1,203 +0,0 @@ -.. _aggregation-pipeline: - -******************** -Aggregation Pipeline -******************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -Mongoid exposes `MongoDB's aggregation pipeline -`_, -which is used to construct flows of operations that process and return results. -The aggregation pipeline is a superset of the deprecated -:ref:`map/reduce framework ` functionality. - - -Basic Usage -=========== - -.. _aggregation-pipeline-example-multiple-collections: - -Querying Across Multiple Collections -```````````````````````````````````` - -The aggregation pipeline may be used for queries involving multiple -referenced associations at the same time: - -.. code-block:: ruby - - class Band - include Mongoid::Document - has_many :tours - has_many :awards - field :name, type: String - end - - class Tour - include Mongoid::Document - belongs_to :band - field :year, type: Integer - end - - class Award - include Mongoid::Document - belongs_to :band - field :name, type: String - end - -To retrieve bands that toured since 2000 and have at least one award, one -could do the following: - -.. code-block:: ruby - - band_ids = Band.collection.aggregate([ - { '$lookup' => { - from: 'tours', - localField: '_id', - foreignField: 'band_id', - as: 'tours', - } }, - { '$lookup' => { - from: 'awards', - localField: '_id', - foreignField: 'band_id', - as: 'awards', - } }, - { '$match' => { - 'tours.year' => {'$gte' => 2000}, - 'awards._id' => {'$exists' => true}, - } }, - {'$project' => {_id: 1}}, - ]) - bands = Band.find(band_ids.to_a) - -Note that the aggregation pipeline, since it is implemented by the Ruby driver -for MongoDB and not Mongoid, returns raw ``BSON::Document`` objects rather than -``Mongoid::Document`` model instances. The above example projects only -the ``_id`` field which is then used to load full models. An alternative is -to not perform such a projection and work with raw fields, which would eliminate -having to send the list of document ids to Mongoid in the second query -(which could be large). - - -.. _aggregation-pipeline-builder-dsl: - -Builder DSL -=========== - -Mongoid provides limited support for constructing the aggregation pipeline -itself using a high-level DSL. The following aggregation pipeline operators -are supported: - -- `$group `_ -- `$project `_ -- `$unwind `_ - -To construct a pipeline, call the corresponding aggregation pipeline methods -on a ``Criteria`` instance. Aggregation pipeline operations are added to the -``pipeline`` attribute of the ``Criteria`` instance. To execute the pipeline, -pass the ``pipeline`` attribute value to ``Collection#aggragegate`` method. - -For example, given the following models: - -.. code-block:: ruby - - class Tour - include Mongoid::Document - - embeds_many :participants - - field :name, type: String - field :states, type: Array - end - - class Participant - include Mongoid::Document - - embedded_in :tour - - field :name, type: String - end - -We can find out which states a participant visited: - -.. code-block:: ruby - - criteria = Tour.where('participants.name' => 'Serenity',). - unwind(:states). - group(_id: 'states', :states.add_to_set => '$states'). - project(_id: 0, states: 1) - - pp criteria.pipeline - # => [{"$match"=>{"participants.name"=>"Serenity"}}, - # {"$unwind"=>"$states"}, - # {"$group"=>{"_id"=>"states", "states"=>{"$addToSet"=>"$states"}}}, - # {"$project"=>{"_id"=>0, "states"=>1}}] - - Tour.collection.aggregate(criteria.pipeline).to_a - - -group -````` - -The ``group`` method adds a `$group aggregation pipeline stage -`_. - -The field expressions support Mongoid symbol-operator syntax: - -.. code-block:: ruby - - criteria = Tour.all.group(_id: 'states', :states.add_to_set => '$states') - criteria.pipeline - # => [{"$group"=>{"_id"=>"states", "states"=>{"$addToSet"=>"$states"}}}] - -Alternatively, standard MongoDB aggregation pipeline syntax may be used: - -.. code-block:: ruby - - criteria = Tour.all.group(_id: 'states', states: {'$addToSet' => '$states'}) - - -project -``````` - -The ``project`` method adds a `$project aggregation pipeline stage -`_. - -The argument should be a Hash specifying the projection: - -.. code-block:: ruby - - criteria = Tour.all.project(_id: 0, states: 1) - criteria.pipeline - # => [{"$project"=>{"_id"=>0, "states"=>1}}] - - -.. _unwind-dsl: - -unwind -`````` - -The ``unwind`` method adds an `$unwind aggregation pipeline stage -`_. - -The argument can be a field name, specifiable as a symbol or a string, or -a Hash or a ``BSON::Document`` instance: - -.. code-block:: ruby - - criteria = Tour.all.unwind(:states) - criteria = Tour.all.unwind('states') - criteria.pipeline - # => [{"$unwind"=>"$states"}] - - criteria = Tour.all.unwind(path: '$states') - criteria.pipeline - # => [{"$unwind"=>{:path=>"$states"}}] diff --git a/source/reference/associations.txt b/source/reference/associations.txt index e8f9c7b3..46d4c880 100644 --- a/source/reference/associations.txt +++ b/source/reference/associations.txt @@ -338,7 +338,7 @@ Querying Referenced Associations In most cases, efficient queries across referenced associations (and in general involving data or conditions or multiple collections) are performed using the aggregation pipeline. Mongoid helpers for constructing aggregation pipeline -queries are described in the :ref:`aggregation pipeline ` +queries are described in the :ref:`aggregation pipeline ` section. For simple queries, the use of aggregation pipeline may be avoided and diff --git a/source/reference/map-reduce.txt b/source/reference/map-reduce.txt index 2251ffa9..48aa0ae5 100644 --- a/source/reference/map-reduce.txt +++ b/source/reference/map-reduce.txt @@ -19,7 +19,7 @@ custom map/reduce jobs or simple aggregations. .. note:: The map-reduce operation is deprecated. - The :ref:`aggregation framework ` provides better + The :ref:`aggregation framework ` provides better performance and usability than map-reduce operations, and should be preferred for new development. diff --git a/source/working-with-data.txt b/source/working-with-data.txt index 2b8b1e91..96ab61dc 100644 --- a/source/working-with-data.txt +++ b/source/working-with-data.txt @@ -12,7 +12,7 @@ Working With Data reference/crud reference/queries reference/text-search - reference/aggregation + /aggregation reference/map-reduce reference/persistence-configuration reference/nested-attributes @@ -28,7 +28,7 @@ See the following sections to learn more about working with data in Mongoid: - :ref:`CRUD Operations ` - :ref:`Queries ` - :ref:`Text Search ` -- :ref:`Aggregation Pipeline ` +- :ref:`mongoid-aggregation` - :ref:`Map/Reduce ` - :ref:`Persistence Configuration ` - :ref:`Nested Attributes ` From e54b4dedbbd16c64d1f1e898638b4b02a28b38b6 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 12 Nov 2024 10:19:51 -0500 Subject: [PATCH 061/113] DOCSP-42774: transactions --- source/includes/interact-data/transaction.rb | 129 +++++++++ source/interact-data.txt | 4 + source/interact-data/transaction.txt | 283 +++++++++++++++++++ 3 files changed, 416 insertions(+) create mode 100644 source/includes/interact-data/transaction.rb create mode 100644 source/interact-data/transaction.txt diff --git a/source/includes/interact-data/transaction.rb b/source/includes/interact-data/transaction.rb new file mode 100644 index 00000000..447bfc6e --- /dev/null +++ b/source/includes/interact-data/transaction.rb @@ -0,0 +1,129 @@ +# start-example-models +class Book + include Mongoid::Document + + field :title, type: String + field :author, type: String + field :length, type: Integer +end + +class Film + include Mongoid::Document + + field :title, type: String + field :year, type: Integer +end +# end-example-models + +# start-txn-operations +# Starts a transaction from the model class +Book.transaction do + Book.create(title: 'Covert Joy', author: 'Clarice Lispector') + Film.create(title: 'Nostalgia', year: 1983) +end + +# Starts a transaction from an instance of Book +book = Book.create(title: 'Sula', author: 'Toni Morrison') +book.transaction do + book.length = 192 + book.save! +end + +# Starts a transaction from the Mongoid instance +Mongoid.transaction do + book.destroy +end +# end-txn-operations + +# start-different-clients +# Defines a class by using the :default client +class Post + include Mongoid::Document +end + +# Defines a class by using the :encrypted_client +class User + include Mongoid::Document + + store_in client: :encrypted_client +end + +# Starts a transaction on the :encrypted_client +User.transaction do + # Uses the same client, so the operation is in the transaction + User.create! + # Uses a different client, so it is *not* in the transaction + Post.create! +end +# end-different-clients + +# start-lower-lvl-api +# Starts a session from the model class +Book.with_session do |session| + # Starts the transaction in the session + session.start_transaction +end + +book = Book.new +# Starts a session from an instance of Book +book.with_session do |session| + # Starts the transaction in the session + session.start_transaction +end +# end-lower-lvl-api + +# start-commit-abort +Book.with_session do |session| + session.commit_transaction +end + +Book.with_session do |session| + session.abort_transaction +end +# end-commit-abort + +# start-commit-retry +begin + session.commit_transaction +rescue Mongo::Error => e + if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL) + retry + else + raise + end +end +# end-commit-retry + +# start-other-client +# Specifies that the operation should use the "other" client instead of +# the default client +User.with(client: :other) do + Post.with(client: :other) do + Post.with_session do |session| + session.start_transaction + Post.create! + Post.create! + User.create! + session.commit_transaction + end + end +end +# end-other-client + +# start-model-session +Book.with_session(causal_consistency: true) do + Book.create! + book = Person.first + book.title = "Swann's Way" + book.save +end +# end-model-session + +# start-instance-session +book = Book.new +book.with_session(causal_consistency: true) do + book.title = 'Catch-22' + book.save + book.sellers << Shop.create! +end +# end-instance-session \ No newline at end of file diff --git a/source/interact-data.txt b/source/interact-data.txt index 442d855e..aa21c568 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -16,6 +16,7 @@ Interact with Data /interact-data/specify-query /interact-data/modify-results + /interact-data/transaction In this section, you can learn how to use {+odm+} to interact with your MongoDB data. @@ -25,3 +26,6 @@ MongoDB data. - :ref:`mongoid-data-modify-results`: Learn how to modify the way that {+odm+} returns results from queries. + +- :ref:`mongoid-data-txn`: Learn how to perform multi-document + transactions to make atomic data changes. \ No newline at end of file diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt new file mode 100644 index 00000000..dca91cc3 --- /dev/null +++ b/source/interact-data/transaction.txt @@ -0,0 +1,283 @@ +.. _mongoid-data-txn: + +========================= +Transactions and Sessions +========================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, ACID compliance, multi-document, ruby odm + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use {+odm+} to perform +**transactions**. Transactions allow you to perform a series of operations +that change data only if the entire transaction is committed. +If any operation in the transaction does not succeed, the driver stops the +transaction and discards all data changes before they ever become +visible. This feature is called **atomicity**. + +In MongoDB, transactions run within logical **sessions**. A +session is a grouping of related read or write operations that you +want to run sequentially. Sessions enable causal consistency for a group +of operations and allow you to run operations in an **ACID-compliant** +transaction that meets an expectation of atomicity, consistency, +isolation, and durability. MongoDB guarantees that the data involved in +your transaction operations remains consistent, even if the operations +encounter unexpected errors. + +In {+odm+}, you can perform transactions by using the following APIs: + +- :ref:`mongoid-txn-high-level`: {+odm+} manages the life cycle of the + transaction. You can use this API in {+odm+} v9.0 and later. + +- :ref:`mongoid-txn-low-level`: You must manage the life cycle of the + transaction. You can use this API in {+odm+} v6.4 and later. + +The :ref:`mongoid-txn-session` section describes how to make changes to +your data from within a session without performing a transaction. + +.. _mongoid-txn-high-level: + +Higher Level API +---------------- + +You can start a transaction by calling the ``transaction()`` method on +an instance of a model, on the model class, or on a ``{+odm+}`` module. + +When you call the ``transaction()`` method, {+odm+} performs the +following tasks: + +1. Creates a session on the client. + +#. Starts a transaction on the session. + +#. Performs the specified data changes. + +#. Commits the transaction if no errors occur or ends the transaction if + there is an error. See the :ref:`` section to learn more an + +#. Closes the session. + +If your transaction is committed, {+odm+} calls any ``after_commit`` +callbacks for all objects modified inside the transaction. If there is +an error and the transaction is rolled back, {+odm+} calls any +``after_rollback`` callbacks for all objects modified inside the +transaction. + +Example +~~~~~~~ + +This example uses the following models to represent documents that +describe books and films: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-example-models + :end-before: end-example-models + :language: ruby + :dedent: + +The following code demonstrates how to perform a transaction on +different objects to change data in multiple collections: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-txn-operations + :end-before: end-txn-operations + :language: ruby + :dedent: + +Client Limitations +~~~~~~~~~~~~~~~~~~ + +Only operations on the same client are in scope of a transaction, +because each transaction is attached to a specific client. Ensure +that you use objects from same client inside the transaction method +block. + +The following example defines model classes that use different clients +and demonstrates how operations are run based on the origin client: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-different-clients + :end-before: end-different-clients + :language: ruby + :dedent: + +.. note:: + + When you call the ``transaction()`` method on the ``{+odm+}`` module, + {+odm+} creates the transaction by using the ``:default`` client. + +Ending Transactions +~~~~~~~~~~~~~~~~~~~ + +Any exception raised inside the transaction method block ends the +transaction and rolls back changes. In most cases, {+odm+} displays the +exception, except for the ``Mongoid::Errors::Rollback`` exception. Implement +this exception in your application to explicitly end the transaction +without passing on an exception. + +Callbacks +~~~~~~~~~ + +This transaction API introduces the ``after_commit`` and +``after_rollback`` callbacks. + +{+odm+} triggers the ``after_commit`` callback for an object that was +created, saved, or destroyed in the following cases: + +- After the transaction is committed if the object was modified inside + the transaction. + +- After the object is persisted if the object was modified before + the transaction block. + +The ``after_commit`` callback is triggered only after all +other callbacks are executed successfully. Therefore, if an object is +modified without a transaction, it is possible that the object was +persisted, but the ``after_commit`` callback was not triggered, for +example, if {+odm+} raised an exception in the ``after_save`` callback. + +The ``after_rollback`` callback is triggered for an object that was +created, saved, or destroyed inside a transaction if the transaction was +ended. {+odm+} never triggers ``after_rollback`` outside of a transaction. + +.. _mongoid-txn-low-level: + +Lower Level API +--------------- + +When using the lower level API, you must create a session before +starting a transaction. You can create a session by calling the +``with_session()`` method on a model class or an instance of a model. + +Then, you can start a transaction by calling the ``start_transaction()`` +method on a session, as shown in the following code: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-lower-lvl-api + :end-before: end-lower-lvl-api + :language: ruby + :dedent: + +You can specify a read concern, write concern or read +preference when starting a transaction by passing options to the +``start_transaction()`` method: + +.. code-block:: ruby + + session.start_transaction( + read_concern: {level: :majority}, + write_concern: {w: 3}, + read: {mode: :primary} + ) + +When using this API, you must manually commit or end the transaction. +You can use the ``commit_transaction()`` and ``abort_transaction()`` +methods on the session instance to manage the transaction lifecycle: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-commit-abort + :end-before: end-commit-abort + :language: ruby + :dedent: + +.. note:: + + If a session ends and includes an open transaction, the transaction is + automatically ended. + +You can retry the transaction commit if it fails, +as shown in the following code: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-commit-retry + :end-before: end-commit-retry + :language: ruby + :dedent: + +Client Behavior +~~~~~~~~~~~~~~~ + +To perform operations within a transaction, operations must use the same +client that the session was initiated on. By default, all operations +are performed by using the default client. + +To explicitly use a different client, use the ``with()`` method: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-other-client + :end-before: end-other-client + :language: ruby + :dedent: + +.. _mongoid-txn-session: + +Perform Data Operations in a Session +------------------------------------ + +You can use sessions in {+odm+} in a similar way that you can +perform a transaction. You can call the ``with_session()`` method on a +model class or on an instance of a model and perform some operations in +a block. All operations in the block will be performed in the context of +single session. + +The following limitations apply when using sessions: + +- You cannot share a session across threads. Sessions are not thread-safe. + +- You cannot nest sessions. You cannot call the ``with_session()`` + method on a model class or a model instance within the block passed to + the ``with_session()`` method on another model class or model instance. + +- All model classes and instances used within the session block must use + the same driver client. For example, if you specify different + ``storage_options`` for another model used in the block than those of the + model or instance that you called ``with_session()`` on, {+odm+} returns + an error. + +Examples +~~~~~~~~ + +You can use the ``with_session()`` on a model class and pass it session +options to perform a block of operations in the context of a session. + +The following code enables the ``causal_consistency`` option when +creating a session, then performs data operations: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-model-session + :end-before: end-model-session + :language: ruby + :dedent: + +Alternatively, you can use the ``with_session()`` on an instance of a +model and pass it session options to perform a block of operations in +the context of a session. + +The following code enables the ``causal_consistency`` option when +creating a session, then performs data operations: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-instance-session + :end-before: end-instance-session + :language: ruby + :dedent: + +Additional Information +---------------------- + +To learn more about transactions, see :manual:`/core/transactions/` in +the {+server-manual+}. + +.. TODO To learn more about performing CRUD operations, see the :ref:`` guide. From 2d23fe4ed74ca88ec0389d0621268ceca0656140 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 12 Nov 2024 10:23:38 -0500 Subject: [PATCH 062/113] vale --- source/interact-data/transaction.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index dca91cc3..c2f3004b 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -122,7 +122,7 @@ Ending Transactions ~~~~~~~~~~~~~~~~~~~ Any exception raised inside the transaction method block ends the -transaction and rolls back changes. In most cases, {+odm+} displays the +transaction and rolls back changes. Usually, {+odm+} displays the exception, except for the ``Mongoid::Errors::Rollback`` exception. Implement this exception in your application to explicitly end the transaction without passing on an exception. @@ -134,7 +134,7 @@ This transaction API introduces the ``after_commit`` and ``after_rollback`` callbacks. {+odm+} triggers the ``after_commit`` callback for an object that was -created, saved, or destroyed in the following cases: +created, saved, or deleted in the following cases: - After the transaction is committed if the object was modified inside the transaction. @@ -149,7 +149,7 @@ persisted, but the ``after_commit`` callback was not triggered, for example, if {+odm+} raised an exception in the ``after_save`` callback. The ``after_rollback`` callback is triggered for an object that was -created, saved, or destroyed inside a transaction if the transaction was +created, saved, or deleted inside a transaction if the transaction was ended. {+odm+} never triggers ``after_rollback`` outside of a transaction. .. _mongoid-txn-low-level: From b68d13e778a4395aa2f92254412fda721c8296e3 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 12 Nov 2024 10:24:03 -0500 Subject: [PATCH 063/113] link text --- source/interact-data/transaction.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index c2f3004b..59b9038d 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -277,7 +277,7 @@ creating a session, then performs data operations: Additional Information ---------------------- -To learn more about transactions, see :manual:`/core/transactions/` in +To learn more about transactions, see :manual:`Transactions ` in the {+server-manual+}. .. TODO To learn more about performing CRUD operations, see the :ref:`` guide. From 348870b32f000448eff6ce81c7f2a23f0757f55f Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 13 Nov 2024 09:38:07 -0500 Subject: [PATCH 064/113] MW PR fixes 1 --- source/interact-data/modify-results.txt | 6 +- source/interact-data/specify-query.txt | 74 ++++++++++++------------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt index 11c57714..70ce8a52 100644 --- a/source/interact-data/modify-results.txt +++ b/source/interact-data/modify-results.txt @@ -255,7 +255,11 @@ The following code retrieves a maximum of ``5`` documents: .. note:: Alternatively, you can use the ``take()`` method to retrieve a - specified number of documents from the database. + specified number of documents from the database: + + .. code-block:: ruby + + Band.take(5) Skip Results ~~~~~~~~~~~~ diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index a188a660..39c12272 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -43,7 +43,7 @@ Sample Data The examples in this guide use the ``Band`` model, which represents a band or musical group. The definition of the ``Band`` model might be different for each section to demonstrate different query -functionalities. Some sections might also use other supplementary models +functionalities. Some sections might use other models to demonstrate query functionality. Queries in {+odm+} @@ -492,9 +492,9 @@ an array field with at least one element that matches all the specified query criteria. The following example creates a sample document that contains an array -field, then uses the ``elem_match()`` method to match documents in -which the ``tour`` array field contains an entry in which the value of -the ``city`` field is ``'London'``: +field. Then, it uses the ``elem_match()`` method to match documents in +which the ``tour`` array field contains an entry in which the ``city`` +value is ``'London'``: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-elem-match-1 @@ -529,7 +529,7 @@ field: .. note:: You cannot use ``elem_match()`` on non-embedded associations because - MongoDB does not perform a join on the collections. + MongoDB does not perform a join operation on the collections. If you perform this query, the conditions are added to the collection that is the source of the non-embedded association rather than the collection of the association. @@ -546,7 +546,7 @@ as shown in the following example: Querying by _id Value --------------------- -{+odm+} provides the ``find()`` method that allows you to query +{+odm+} provides the ``find()`` method, which allows you to query documents by their ``_id`` values. The following example uses the ``find()`` method to match a document @@ -558,8 +558,8 @@ with the specified ``_id`` field value: .. note:: Type Conversion - The ``find()`` method performs the type conversion of the argument that - you pass to the type declared for the ``_id`` field in the model. By + The ``find()`` method converts the ID that you pass to the + data type declared for the ``_id`` field in the model. By default, the ``_id`` field is defined as a ``BSON::ObjectId`` type. The preceding example is equivalent to the following code, which @@ -596,13 +596,13 @@ The ``find()`` method exhibits the following behavior: depends on the value of the ``raise_not_found_error`` configuration option. - If you set the option to ``true``, ``find()`` raises a - ``Mongoid::Errors::DocumentNotFound`` error if any of the ``_id`` - values are not found. + If you set the ``raise_not_found_error`` option to ``true``, + ``find()`` raises a ``Mongoid::Errors::DocumentNotFound`` error if any + of the ``_id`` values are not found. - If you set the option to ``false`` and query for a single ``_id`` - value, ``find()`` returns ``nil`` if {+odm+} does not match any - document. If you pass multiple ``_id`` values and + If you set the ``raise_not_found_error`` option to ``false`` and query + for a single ``_id`` value, ``find()`` returns ``nil`` if {+odm+} does + not match a document. If you pass multiple ``_id`` values and some or all are not matched, the return value is an array of any documents that match, or an empty array if no documents match. @@ -627,7 +627,7 @@ The following code demonstrates how to use the ``find_by()`` method: You can use the ``find_or_create_by()`` method to retrieve documents based on the provided criteria. If no documents are found, it creates -and returns a newly persisted one. +and returns an instance that is saved to MongoDB. The following code demonstrates how to use the ``find_or_create_by()`` method: @@ -638,10 +638,11 @@ method: :language: ruby :dedent: -You can use the ``find_or_initialize_by()`` method to to retrieve documents +You can use the ``find_or_initialize_by()`` method to retrieve documents based on the provided criteria. If no documents are found, it returns a -new one, without persisting it. The syntax for -``find_or_initialize_by()`` is the same as in the preceding example. +new one, without persisting it to MongoDB. The syntax for +``find_or_initialize_by()`` is the same for the ``find_or_create_by()`` +method. Regular Expressions ------------------- @@ -664,8 +665,8 @@ shown in the following code: Band.where(name: /impala/i) # => Returns sample document -You can also perform queries by using PCRE syntax and -``BSON::Regexp::Raw`` objects: +You can also perform queries by using Perl Compatible Regular Expression +(PCRE) syntax and ``BSON::Regexp::Raw`` objects: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-regex @@ -676,12 +677,12 @@ You can also perform queries by using PCRE syntax and Field Type Query Conversions ---------------------------- -When you specify a query on a field defined in a model, {+odm+} converts -the query value based on how the field is defined, and if it has a -specified type. +When you specify a query on a field defined in a model, if the field has +a *specified data type*, {+odm+} converts the query value based on how the +field is defined. Consider the following ``Album`` model definition that -contains a ``Date``-valued field, a ``Time``-valued field and an +contains a ``Date``-valued field, a ``Time``-valued field, and an implicit ``Object``-valued field. The model also intentionally *does not define* a field named ``last_reviewed``: @@ -718,7 +719,7 @@ In the preceding example, the following conversions apply: ``last_commented`` field, {+odm+} interprets the date to be in local time and applies the :ref:`configured time zone <>`. -- When querying on the ``last_purchased`` field that has no explicit +- When querying on the ``last_purchased`` field, which has no explicit type, the date is used unmodified in the constructed query. - When querying on the undefined ``last_reviewed`` field, {+odm+} @@ -792,9 +793,6 @@ result from the list of returned documents based on its position. - ``second()``: Returns the second matching document. Automatically uses a sort on the ``_id`` field. -- ``second()``: Returns the second matching document. Automatically uses - a sort on the ``_id`` field. - - ``third()``: Returns the third matching document. Automatically uses a sort on the ``_id`` field. @@ -804,11 +802,11 @@ result from the list of returned documents based on its position. - ``fifth()``: Returns the fifth matching document. Automatically uses a sort on the ``_id`` field. -- ``second_to_last()``: Returns the second to last matching document. - Automatically usesa sort on the ``_id`` field. *See line 14 in the +- ``second_to_last()``: Returns the second-to-last matching document. + Automatically uses a sort on the ``_id`` field. *See line 14 in the following code for an example.* -- ``third_to_last()``: Returns the third to last matching document. +- ``third_to_last()``: Returns the third-to-last matching document. Automatically uses a sort on the ``_id`` field. The following code demonstrates how to use some methods described @@ -823,11 +821,11 @@ in the preceding list: .. tip:: Error Generation - Each method described in this section has a ``-!`` suffixed counterpart - that returns an error if {+odm+} doesn't match any documents. For - example, to implement error handling in your application when your - query returns no results, use the ``first!()`` method instead of - ``first()``. + Each method described in this section has a variation that is + suffixed with ``!`` that returns an error if {+odm+} doesn't match + any documents. For example, to implement error handling in your + application when your query returns no results, use the ``first!()`` + method instead of ``first()``. Survey Field Values ~~~~~~~~~~~~~~~~~~~ @@ -900,8 +898,8 @@ category: Additional Information ---------------------- -To learn how to modify how {+odm+} returns results to you, see +To learn how to modify the way that {+odm+} returns results to you, see :ref:`mongoid-data-modify-results`. To learn more about defining scopes on your models, see -:ref:`mongoid-data-scoping`. \ No newline at end of file +:ref:`mongoid-data-scoping`. From 6a11623e3ba9c62045723e39a51a12cac2ca6596 Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 13 Nov 2024 12:28:08 -0500 Subject: [PATCH 065/113] MR PR fixes 1 --- snooty.toml | 1 + source/includes/interact-data/transaction.rb | 3 + source/interact-data/transaction.txt | 121 +++++++++++++------ 3 files changed, 85 insertions(+), 40 deletions(-) diff --git a/snooty.toml b/snooty.toml index 5b4584c2..c9983563 100644 --- a/snooty.toml +++ b/snooty.toml @@ -27,3 +27,4 @@ feedback-widget-title = "Feedback" server-manual = "Server manual" api-root = "https://www.mongodb.com/docs/mongoid/master/api/Mongoid" api = "https://www.mongodb.com/docs/mongoid/master/api" +ruby-api = "https://www.mongodb.com/docs/ruby-driver/current/api/Mongo" diff --git a/source/includes/interact-data/transaction.rb b/source/includes/interact-data/transaction.rb index 447bfc6e..fdfa3025 100644 --- a/source/includes/interact-data/transaction.rb +++ b/source/includes/interact-data/transaction.rb @@ -18,6 +18,7 @@ class Film # start-txn-operations # Starts a transaction from the model class Book.transaction do + # Saves new Book and Film instances to MongoDB Book.create(title: 'Covert Joy', author: 'Clarice Lispector') Film.create(title: 'Nostalgia', year: 1983) end @@ -25,12 +26,14 @@ class Film # Starts a transaction from an instance of Book book = Book.create(title: 'Sula', author: 'Toni Morrison') book.transaction do + # Saves a new field value to the Book instance book.length = 192 book.save! end # Starts a transaction from the Mongoid instance Mongoid.transaction do + # Deletes the Book instance in MongoDB book.destroy end # end-txn-operations diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index 59b9038d..b10c6058 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -25,32 +25,40 @@ In this guide, you can learn how to use {+odm+} to perform that change data only if the entire transaction is committed. If any operation in the transaction does not succeed, the driver stops the transaction and discards all data changes before they ever become -visible. This feature is called **atomicity**. +visible. This feature is called :wikipedia:`atomicity `. In MongoDB, transactions run within logical **sessions**. A session is a grouping of related read or write operations that you want to run sequentially. Sessions enable causal consistency for a group -of operations and allow you to run operations in an **ACID-compliant** +of operations, which means that all processes in your application agree on +the order of causally-related operations. + +Sessions allow you to run operations in an **ACID-compliant** transaction that meets an expectation of atomicity, consistency, isolation, and durability. MongoDB guarantees that the data involved in your transaction operations remains consistent, even if the operations encounter unexpected errors. -In {+odm+}, you can perform transactions by using the following APIs: +In {+odm+}, you can perform transactions by using either of the +following APIs: -- :ref:`mongoid-txn-high-level`: {+odm+} manages the life cycle of the +- :ref:`mongoid-txn-convenient`: {+odm+} manages the life cycle of the transaction. You can use this API in {+odm+} v9.0 and later. -- :ref:`mongoid-txn-low-level`: You must manage the life cycle of the +- :ref:`mongoid-txn-core`: You must manage the life cycle of the transaction. You can use this API in {+odm+} v6.4 and later. The :ref:`mongoid-txn-session` section describes how to make changes to your data from within a session without performing a transaction. -.. _mongoid-txn-high-level: +.. _mongoid-txn-convenient: + +Convenient Transaction API +-------------------------- -Higher Level API ----------------- +You can use the Convenient Transaction API to internally manage the +lifecycle of your transaction. This API either commits your transaction +or ends it and incorporates error handling logic. You can start a transaction by calling the ``transaction()`` method on an instance of a model, on the model class, or on a ``{+odm+}`` module. @@ -65,7 +73,7 @@ following tasks: #. Performs the specified data changes. #. Commits the transaction if no errors occur or ends the transaction if - there is an error. See the :ref:`` section to learn more an + there is an error. #. Closes the session. @@ -73,7 +81,8 @@ If your transaction is committed, {+odm+} calls any ``after_commit`` callbacks for all objects modified inside the transaction. If there is an error and the transaction is rolled back, {+odm+} calls any ``after_rollback`` callbacks for all objects modified inside the -transaction. +transaction. To learn more about this behavior, see the +:ref:`mongoid-txn-callbacks` section of this guide. Example ~~~~~~~ @@ -96,12 +105,12 @@ different objects to change data in multiple collections: :language: ruby :dedent: -Client Limitations -~~~~~~~~~~~~~~~~~~ +Client Behavior +~~~~~~~~~~~~~~~ -Only operations on the same client are in scope of a transaction, +Only operations on the same client are in the scope of a transaction, because each transaction is attached to a specific client. Ensure -that you use objects from same client inside the transaction method +that you use objects from the same client inside the transaction method block. The following example defines model classes that use different clients @@ -122,10 +131,14 @@ Ending Transactions ~~~~~~~~~~~~~~~~~~~ Any exception raised inside the transaction method block ends the -transaction and rolls back changes. Usually, {+odm+} displays the -exception, except for the ``Mongoid::Errors::Rollback`` exception. Implement -this exception in your application to explicitly end the transaction -without passing on an exception. +transaction and rolls back data changes. {+odm+} displays all +exceptions except for the ``Mongoid::Errors::Rollback`` exception. You +must raise this exception in your application to explicitly end the +transaction without returning the exception to you. You might implement +this transaction exception to end a transaction when a certain condition +is not met without raising an exception message. + +.. _mongoid-txn-callbacks: Callbacks ~~~~~~~~~ @@ -143,21 +156,26 @@ created, saved, or deleted in the following cases: the transaction block. The ``after_commit`` callback is triggered only after all -other callbacks are executed successfully. Therefore, if an object is -modified without a transaction, it is possible that the object was -persisted, but the ``after_commit`` callback was not triggered, for -example, if {+odm+} raised an exception in the ``after_save`` callback. +other callbacks are performed successfully. Therefore, if an object is +modified outside of a transaction, it is possible that the object is then +persisted, but the ``after_commit`` callback is not triggered. This +might occur, for example, if {+odm+} raised an exception in the +``after_save`` callback because this callback must complete successfully +to trigger ``after_commit``. The ``after_rollback`` callback is triggered for an object that was created, saved, or deleted inside a transaction if the transaction was -ended. {+odm+} never triggers ``after_rollback`` outside of a transaction. +ended. {+odm+} never triggers ``after_rollback`` outside of a +transaction. + +.. TODO link to callbacks guide. -.. _mongoid-txn-low-level: +.. _mongoid-txn-core: -Lower Level API ---------------- +Core Transaction API +-------------------- -When using the lower level API, you must create a session before +When using the Core API, you must create a session before starting a transaction. You can create a session by calling the ``with_session()`` method on a model class or an instance of a model. @@ -182,6 +200,11 @@ preference when starting a transaction by passing options to the read: {mode: :primary} ) +To learn more about the available transaction options, see +`start_transaction() +<{+ruby-api+}/Session.html#start_transaction-instance_method>`__ in the +{+ruby-driver+} API documentation. + When using this API, you must manually commit or end the transaction. You can use the ``commit_transaction()`` and ``abort_transaction()`` methods on the session instance to manage the transaction lifecycle: @@ -197,8 +220,9 @@ methods on the session instance to manage the transaction lifecycle: If a session ends and includes an open transaction, the transaction is automatically ended. -You can retry the transaction commit if it fails, -as shown in the following code: +You can retry the transaction commit if it fails initially. The +following example demonstrates how to retry the transaction when {+odm+} +raises the ``UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL`` exception: .. literalinclude:: /includes/interact-data/transaction.rb :start-after: start-commit-retry @@ -223,8 +247,8 @@ To explicitly use a different client, use the ``with()`` method: .. _mongoid-txn-session: -Perform Data Operations in a Session ------------------------------------- +Session API +----------- You can use sessions in {+odm+} in a similar way that you can perform a transaction. You can call the ``with_session()`` method on a @@ -236,24 +260,35 @@ The following limitations apply when using sessions: - You cannot share a session across threads. Sessions are not thread-safe. -- You cannot nest sessions. You cannot call the ``with_session()`` - method on a model class or a model instance within the block passed to - the ``with_session()`` method on another model class or model instance. +- You cannot nest sessions. For example, you cannot call the ``with_session()`` + method on a model class within the block passed to + the ``with_session()`` method on another model class. The following + code demonstrates a nested session that results in an error: + + .. code-block:: ruby + + Book.with_session(causal_consistency: true) do + # Nesting sessions results in errors + Film.with_session(causal_consistency: true) do + ... + end + end - All model classes and instances used within the session block must use the same driver client. For example, if you specify different - ``storage_options`` for another model used in the block than those of the + a different client for a model used in the block than those of the model or instance that you called ``with_session()`` on, {+odm+} returns an error. Examples ~~~~~~~~ -You can use the ``with_session()`` on a model class and pass it session +You can use the ``with_session()`` method on a model class and pass it session options to perform a block of operations in the context of a session. -The following code enables the ``causal_consistency`` option when -creating a session, then performs data operations: +The following code enables the ``causal_consistency`` option to +guarantee the order of operations when creating a session, then +performs data operations: .. literalinclude:: /includes/interact-data/transaction.rb :start-after: start-model-session @@ -261,12 +296,18 @@ creating a session, then performs data operations: :language: ruby :dedent: +To learn more about the available session options, see the +`Session class constructor details +<{+ruby-api+}/Session.html#initialize-instance_method>`__ in the +{+ruby-driver+} API documentation. + Alternatively, you can use the ``with_session()`` on an instance of a model and pass it session options to perform a block of operations in the context of a session. -The following code enables the ``causal_consistency`` option when -creating a session, then performs data operations: +The following code enables the ``causal_consistency`` option to +guarantee the order of operations when creating a session, then +performs data operations: .. literalinclude:: /includes/interact-data/transaction.rb :start-after: start-instance-session From 9814b0b8796125bf6518655b862c20b4db7a7c2e Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 13 Nov 2024 14:56:43 -0500 Subject: [PATCH 066/113] DOCSP-45306: model data drawer --- source/data-modeling.txt | 21 +++++++++++++++++++++ source/index.txt | 9 +++++---- source/interact-data.txt | 4 ++-- 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 source/data-modeling.txt diff --git a/source/data-modeling.txt b/source/data-modeling.txt new file mode 100644 index 00000000..45c78994 --- /dev/null +++ b/source/data-modeling.txt @@ -0,0 +1,21 @@ +.. _mongoid-data-modeling: + +=============== +Model Your Data +=============== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, model, class, query + +.. .. toctree:: +.. :caption: Data Modeling +.. +.. Documents + +In this section, you can learn how to model data in {+odm+}. + +.. - :ref:``: Learn how to ... diff --git a/source/index.txt b/source/index.txt index 01d41313..53f3a002 100644 --- a/source/index.txt +++ b/source/index.txt @@ -12,10 +12,11 @@ MongoDB in Ruby. To work with {+odm+} from the command line using .. toctree:: :titlesonly: - /quick-start-rails - /quick-start-sinatra - /add-existing - /interact-data + Quick Start - Ruby on Rails + Quick Start - Sinatra + Add {+odm+} to an Existing Application + Interact with Data + Model Your Data installation-configuration tutorials schema-configuration diff --git a/source/interact-data.txt b/source/interact-data.txt index 442d855e..947b7a53 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -14,8 +14,8 @@ Interact with Data .. toctree:: :caption: Interact with Data - /interact-data/specify-query - /interact-data/modify-results + Specify a Query + Modify Query Results In this section, you can learn how to use {+odm+} to interact with your MongoDB data. From 9603d85c47acc30cae7e1186078bc0dbc176bfa1 Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 14 Nov 2024 10:44:16 -0500 Subject: [PATCH 067/113] DOCSP-45330: inheritance (WIP) --- source/data-modeling.txt | 11 ++-- source/data-modeling/inheritance.txt | 54 ++++++++++++++++++++ source/includes/data-modeling/inheritance.rb | 17 ++++++ 3 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 source/data-modeling/inheritance.txt create mode 100644 source/includes/data-modeling/inheritance.rb diff --git a/source/data-modeling.txt b/source/data-modeling.txt index 45c78994..b76bc094 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -11,11 +11,12 @@ Model Your Data .. meta:: :keywords: ruby framework, odm, model, class, query -.. .. toctree:: -.. :caption: Data Modeling -.. -.. Documents +.. toctree:: + :caption: Data Modeling + + Inheritance In this section, you can learn how to model data in {+odm+}. -.. - :ref:``: Learn how to ... +- :ref:`mongoid-modeling-inheritance`: Learn how to implement + inheritance in your model classes. diff --git a/source/data-modeling/inheritance.txt b/source/data-modeling/inheritance.txt new file mode 100644 index 00000000..523eab08 --- /dev/null +++ b/source/data-modeling/inheritance.txt @@ -0,0 +1,54 @@ +.. _mongoid-modeling-inheritance: + +=========== +Inheritance +=========== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, relationship, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to implement **inheritance** into your +{+odm+} models. Inheritance allows you to apply the characteristics of +one class to another class. + +{+odm+} supports inheritance in top level and embedded documents. +When a child model class inherits from a parent class, {+odm+} copies +the parent class's fields, associations, validations, and scopes to +the child class. + +Assign Inheritance +------------------ + +When creating a child model class, use the ``<`` character to implement +inheritance from a specified parent class. The following model classes +demonstrate how to create parent and child classes between the +``Person``, ``Employee``, and ``Manager`` models: + +.. literalinclude:: /includes/data-modeling/inheritance.rb + :start-after: start-simple-inheritance + :end-before: end-simple-inheritance + :language: ruby + :emphasize-lines: 7, 14 + :dedent: + +When you perform data operations by using the preceding models, any +instances of ``Person``, ``Employee``, or ``Manager`` are all saved in the +``people`` collection. {+odm+} adds the ``_type`` discriminator field to +each document to ensure that documents are returned as the expected +types when you retrieve them. + +You can also embed + diff --git a/source/includes/data-modeling/inheritance.rb b/source/includes/data-modeling/inheritance.rb new file mode 100644 index 00000000..ec76d604 --- /dev/null +++ b/source/includes/data-modeling/inheritance.rb @@ -0,0 +1,17 @@ +# start-simple-inheritance +class Person + include Mongoid::Document + + field :name, type: String +end + +class Employee < Person + field :company, type: String + field :tenure, type: Integer + + scope :new_hire, ->{ where(:tenure.lt => 1) } +end + +class Manager < Employee +end +# end-simple-inheritance \ No newline at end of file From c0eda4b6eed6788aed0810b9e8d7d0b8738fd397 Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 14 Nov 2024 10:49:25 -0500 Subject: [PATCH 068/113] MR PR fixes 2 --- source/interact-data/transaction.txt | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index b10c6058..ed24faca 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -72,8 +72,8 @@ following tasks: #. Performs the specified data changes. -#. Commits the transaction if no errors occur or ends the transaction if - there is an error. +#. Commits the transaction to the database if no errors occur, or ends + the transaction if there is an error. #. Closes the session. @@ -81,8 +81,8 @@ If your transaction is committed, {+odm+} calls any ``after_commit`` callbacks for all objects modified inside the transaction. If there is an error and the transaction is rolled back, {+odm+} calls any ``after_rollback`` callbacks for all objects modified inside the -transaction. To learn more about this behavior, see the -:ref:`mongoid-txn-callbacks` section of this guide. +transaction. To learn more about these callbacks and their behavior, see +the :ref:`mongoid-txn-callbacks` section of this guide. Example ~~~~~~~ @@ -133,10 +133,10 @@ Ending Transactions Any exception raised inside the transaction method block ends the transaction and rolls back data changes. {+odm+} displays all exceptions except for the ``Mongoid::Errors::Rollback`` exception. You -must raise this exception in your application to explicitly end the -transaction without returning the exception to you. You might implement -this transaction exception to end a transaction when a certain condition -is not met without raising an exception message. +can raise this exception in your application to explicitly end the +transaction without returning the exception to you. For example, you +might implement this transaction exception to end a transaction when a +certain condition is not met, but without raising an exception message. .. _mongoid-txn-callbacks: @@ -164,9 +164,9 @@ might occur, for example, if {+odm+} raised an exception in the to trigger ``after_commit``. The ``after_rollback`` callback is triggered for an object that was -created, saved, or deleted inside a transaction if the transaction was -ended. {+odm+} never triggers ``after_rollback`` outside of a -transaction. +created, saved, or deleted inside a transaction, if the transaction was +unsuccessful and changes were rolled back. {+odm+} never triggers +``after_rollback`` outside of a transaction. .. TODO link to callbacks guide. @@ -287,8 +287,8 @@ You can use the ``with_session()`` method on a model class and pass it session options to perform a block of operations in the context of a session. The following code enables the ``causal_consistency`` option to -guarantee the order of operations when creating a session, then -performs data operations: +guarantee the order of operations when creating a session on the +``Book`` model, then performs data operations: .. literalinclude:: /includes/interact-data/transaction.rb :start-after: start-model-session @@ -301,13 +301,13 @@ To learn more about the available session options, see the <{+ruby-api+}/Session.html#initialize-instance_method>`__ in the {+ruby-driver+} API documentation. -Alternatively, you can use the ``with_session()`` on an instance of a +Alternatively, you can use the ``with_session()`` method on an instance of a model and pass it session options to perform a block of operations in the context of a session. The following code enables the ``causal_consistency`` option to -guarantee the order of operations when creating a session, then -performs data operations: +guarantee the order of operations when creating a session on an instance +of ``Book``, then performs data operations: .. literalinclude:: /includes/interact-data/transaction.rb :start-after: start-instance-session From d1e3bf3c6e155b5f8f7aec6113c4e409b073efbc Mon Sep 17 00:00:00 2001 From: rustagir Date: Thu, 14 Nov 2024 10:54:41 -0500 Subject: [PATCH 069/113] try using roles --- source/interact-data/transaction.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index ed24faca..2c1b39c5 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -201,8 +201,8 @@ preference when starting a transaction by passing options to the ) To learn more about the available transaction options, see -`start_transaction() -<{+ruby-api+}/Session.html#start_transaction-instance_method>`__ in the +:ruby-api:`start_transaction() +` in the {+ruby-driver+} API documentation. When using this API, you must manually commit or end the transaction. @@ -297,8 +297,8 @@ guarantee the order of operations when creating a session on the :dedent: To learn more about the available session options, see the -`Session class constructor details -<{+ruby-api+}/Session.html#initialize-instance_method>`__ in the +:ruby-api:`Session class constructor details +` in the {+ruby-driver+} API documentation. Alternatively, you can use the ``with_session()`` method on an instance of a From c4c393315253b511d3d27cbbe57c9e510eb48cff Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 15 Nov 2024 15:30:43 -0500 Subject: [PATCH 070/113] wip --- source/data-modeling/inheritance.txt | 264 ++++++++++++++++++- source/includes/data-modeling/inheritance.rb | 61 ++++- 2 files changed, 318 insertions(+), 7 deletions(-) diff --git a/source/data-modeling/inheritance.txt b/source/data-modeling/inheritance.txt index 523eab08..83492d96 100644 --- a/source/data-modeling/inheritance.txt +++ b/source/data-modeling/inheritance.txt @@ -22,7 +22,7 @@ Overview In this guide, you can learn how to implement **inheritance** into your {+odm+} models. Inheritance allows you to apply the characteristics of -one class to another class. +one "parent" class to one or more "child" classes. {+odm+} supports inheritance in top level and embedded documents. When a child model class inherits from a parent class, {+odm+} copies @@ -44,11 +44,263 @@ demonstrate how to create parent and child classes between the :emphasize-lines: 7, 14 :dedent: -When you perform data operations by using the preceding models, any +When you perform data operations by using the preceding models, instances of ``Person``, ``Employee``, or ``Manager`` are all saved in the -``people`` collection. {+odm+} adds the ``_type`` discriminator field to -each document to ensure that documents are returned as the expected -types when you retrieve them. +``people`` collection. {+odm+} sets the ``_type`` discriminator field to +the model class name in documents to ensure that documents are returned +as the expected types when you retrieve them. -You can also embed +Embedded Documents +~~~~~~~~~~~~~~~~~~ +You can also implement an inheritance pattern in embedded associations. +Similar to the behavior of top-level model classes, {+odm+} sets the +``_type`` discriminator field in embedded documents depending on the +model class used to create them. + +The following example adds an embedded association to the ``Person`` +model and create parent and child models for the embedded ``Info`` +class: + +.. literalinclude:: /includes/data-modeling/inheritance.rb + :start-after: start-embedded-inheritance + :end-before: end-embedded-inheritance + :language: ruby + :emphasize-lines: 5, 14, 17, 22 + :dedent: + +Query Behavior +~~~~~~~~~~~~~~ + +When you query on a child model class, the query returns only documents +in which the value of the ``_type`` field match the queried class or +further child classes. For example, if you query on the ``Employee`` +class, the query returns documents from the ``people`` collection in +which the ``_type`` value is either ``"Employee"`` or ``"Manager"``. All +other discriminator values are considered as instances of the parent +``Person`` class. + +When querying on a parent class such as ``Person``, {+odm+} returns +documents that meet any of the following criteria: + +- Discriminator value is the name of the parent class or any of the + child classes. For example, ``"Person"``, ``"Employee"``, or ``"Manager"``. + +- Lacks a discriminator value. + +- Discriminator value does not map to either the parent or any of its + child classes. For example, ``"Director"`` or ``"Specialist"``. + +Change the Discriminator Key +---------------------------- + +You might change the discriminator key from the default field name +``_type`` for any of the following reasons: + +- Optimization: You can select a shorter key such as ``_t``. + +- Consistency with an existing system: You might be using an existing + system or dataset that has predefined keys. + +You can change the discriminator key on the class level +or on the global level. To change the discriminator key on the class +level, you can set the custom key name on the parent class by using the +``discriminator_key()`` method. + +The following example demonstrates how to set a custom discriminator key +when defining a model class: + +.. code-block:: ruby + :emphasize-lines: 6 + + class Person + include Mongoid::Document + + field :name, type: String + + self.discriminator_key = "sub_type" + end + +When you create an instance of ``Person`` or any of its child classes, +{+odm+} adds the ``sub_type`` field to documents in MongoDB. + +.. note:: + + You can change the discriminator key only on the parent class. + {+odm+} raises an error if you set a custom key on any child + class. + +If you change the discriminator key after defining a child class, {+odm+} +adds the new key field, but the old field is unchanged. For example, suppose +you add the following code to your application *after* defining your +model classes: + +.. code-block:: ruby + + Person.discriminator_key = "sub_type" + +In this case, when you create an instance of a child class such as +``Employee``, {+odm+} adds both the ``sub_type`` and ``_type`` fields to +the document. + +You can also change the discriminator key at the global level, so that +all classes use the specified key instead of the ``_type`` field. + +You can set a global key by adding the following code to your +application *before* defining any model classes: + +.. code-block:: ruby + + Mongoid.discriminator_key = "sub_type" + +All classes use ``sub_type`` as the discriminator key and do not include +the ``_type`` field. + +.. note:: + + You must set the discriminator key on the global level before defining + any child classes for the classes to use that global value. If you set + the global key after defining child classes, your saved documents + contain the default ``_type`` field. + +Change the Discriminator Value +------------------------------ + +You can customize the value that {+odm+} sets as the discriminator value +in MongoDB. Use the ``discriminator_value()`` method when defining a +class to customize the discriminator value, as shown in the following +example: + +.. code-block:: ruby + :emphasize-lines: 6 + + class Employee + include Mongoid::Document + + field :company, type: String + + self.discriminator_value = "Worker" + end + +When you create an instance of ``Employee``, the document's ``_type`` +discriminator field has a value of ``"Worker"`` instead of the class +name. + +.. note:: + + Because the discriminator value customization is declared in child classes, + you must load the child classes retrieved by a query *before* sending + that query. In the preceding example, the ``Employee`` class definition + must be loaded before you query on ``Person`` if the returned documents could + potentially be instances of ``Employee``. Autoloading isn't able to resolve + the discriminator value ``"Worker"`` to return the document as an + instance of ``Employee``. + +Embedded Associations +--------------------- + +You can create any type of parent class or child class in an embedded +association by assignment or by using the ``build()`` and ``create()`` +methods. You can pass desired model class as the second parameter to the +``build()`` and ``create()`` methods to instruct {+odm+} to create that +specific instance as an emdedded document. + +The following code creates an instance of ``Employee``, then +demonstrates how to add embedded documents by using the different +creation methods: + +.. literalinclude:: /includes/data-modeling/inheritance.rb + :start-after: start-association-operations + :end-before: end-association-operations + :language: ruby + :dedent: + +The following document is stored in the ``people`` database: + +.. code-block:: json + :emphasize-lines: 13, 20, 25, 32 + + { + "_id": {...}, + "name": "Lance Huang", + "company": "XYZ Communications", + "tenure": 2, + "_type": "Employee", + "infos": [ + { + "_id": {...}, + "active": true, + "value": "l.huang@company.com", + "category": "work", + "_type": "Email" + }, + { + "_id": {...}, + "active": false, + "value": "lanceh11@mymail.com", + "category": "personal", + "_type": "Email" + }, + { + "_id": {...}, + "active": true, + "_type": "Info" + }, + { + "_id": {...}, + "active": true, + "value": 1239007777, + "country": "USA", + "_type": "Phone" + } + ] + } + +Persistence Contexts +-------------------- + +You can change the persistence context of a child class from +the persistence context of its parent to store the document in a +different location than the default. By using the ``store_in()`` method, +you can store an instance of a child class in a different collection, +database, or cluster than an instance of the parent model. + +The following model definitions demonstrate how to use the +``store_in()`` method to store instances of ``Employee`` and ``Manager`` +in a different collection than the ``people`` collection: + +.. code-block:: ruby + :emphasize-lines: 6, 10 + + class Person + include Mongoid::Document + end + + class Employee < Person + # Specifies "employees" as target collection + store_in collection: :employees + end + + class Manager < Employee + # Specifies "managers" as target collection + store_in collection: :managers + end + +.. note:: + + {+odm+} still adds the discriminator field to stored documents. + +If you set an alternate target collection on some of the child classes +and not others, instances of the classes without specified collections +are stored in the collection associated with the parent class. + +.. note:: + + When you change the target collection for a child class, instances of + that class do not appear in the results from queries on the parent + class. + +Additional Information +---------------------- + +.. TODO add links to persistence docs diff --git a/source/includes/data-modeling/inheritance.rb b/source/includes/data-modeling/inheritance.rb index ec76d604..d54a468b 100644 --- a/source/includes/data-modeling/inheritance.rb +++ b/source/includes/data-modeling/inheritance.rb @@ -14,4 +14,63 @@ class Employee < Person class Manager < Employee end -# end-simple-inheritance \ No newline at end of file +# end-simple-inheritance + +# start-embedded-inheritance +class Person + include Mongoid::Document + + field :name, type: String + embeds_many :infos +end + +... + +class Info + include Mongoid::Document + + field :active, type: Boolean + embedded_in :person +end + +class Phone < Info + field :value, type: Float + field :country, type: String +end + +class Email < Info + field :value, type: String + field :category, type: String +end +# end-embedded-inheritance + +# start-association-operations +# Creates a new Employee instance +e = Employee.create( + name: "Lance Huang", + company: "XYZ Communications", + tenure: 2 +) + +# Builds an Info object +e.infos.build({ active: true }) + +# Builds a Phone object +e.infos.build( + { active: true, value: 1239007777, country: "USA" }, + Phone +) + +# Creates an Email object +e.infos.create( + { active: true, value: "l.huang@company.com", category: "work" }, + Email +) + +# Creates and assigns an Email object +p = Email.new(active: false, value: "lanceh11@mymail.com", category: "personal" ) +e.infos << p + +# Saves the Employee instance to database +e.save +# end-association-operations From ff70eaf8b610a0220d9e20e87b8572e6fe481706 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 15 Nov 2024 15:33:00 -0500 Subject: [PATCH 071/113] vale --- source/data-modeling/inheritance.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/data-modeling/inheritance.txt b/source/data-modeling/inheritance.txt index 83492d96..017e00a3 100644 --- a/source/data-modeling/inheritance.txt +++ b/source/data-modeling/inheritance.txt @@ -290,7 +290,7 @@ in a different collection than the ``people`` collection: {+odm+} still adds the discriminator field to stored documents. -If you set an alternate target collection on some of the child classes +If you set an alternate target collection on some child classes and not others, instances of the classes without specified collections are stored in the collection associated with the parent class. From 8fbf2cae59a013aee908a94a22f11a31ef8822e9 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 15 Nov 2024 15:35:06 -0500 Subject: [PATCH 072/113] add label --- source/data-modeling/inheritance.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/data-modeling/inheritance.txt b/source/data-modeling/inheritance.txt index 017e00a3..c1fd5a6a 100644 --- a/source/data-modeling/inheritance.txt +++ b/source/data-modeling/inheritance.txt @@ -9,7 +9,7 @@ Inheritance :values: reference .. meta:: - :keywords: ruby framework, odm, relationship, code example + :keywords: ruby framework, odm, relationship, code example, polymorphic .. contents:: On this page :local: From 81afbe841866cd647eb125595102030efa319192 Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 15 Nov 2024 15:38:19 -0500 Subject: [PATCH 073/113] fixes --- source/data-modeling/inheritance.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/data-modeling/inheritance.txt b/source/data-modeling/inheritance.txt index c1fd5a6a..10747cd3 100644 --- a/source/data-modeling/inheritance.txt +++ b/source/data-modeling/inheritance.txt @@ -59,7 +59,7 @@ Similar to the behavior of top-level model classes, {+odm+} sets the model class used to create them. The following example adds an embedded association to the ``Person`` -model and create parent and child models for the embedded ``Info`` +model and creates parent and child models for the embedded ``Info`` class: .. literalinclude:: /includes/data-modeling/inheritance.rb @@ -270,7 +270,7 @@ The following model definitions demonstrate how to use the in a different collection than the ``people`` collection: .. code-block:: ruby - :emphasize-lines: 6, 10 + :emphasize-lines: 7, 11 class Person include Mongoid::Document From f006774ae0268d84f9bfd241c5823c5447bb7b2f Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 15 Nov 2024 15:38:41 -0500 Subject: [PATCH 074/113] fix --- source/data-modeling/inheritance.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/data-modeling/inheritance.txt b/source/data-modeling/inheritance.txt index 10747cd3..bc426aa6 100644 --- a/source/data-modeling/inheritance.txt +++ b/source/data-modeling/inheritance.txt @@ -270,7 +270,7 @@ The following model definitions demonstrate how to use the in a different collection than the ``people`` collection: .. code-block:: ruby - :emphasize-lines: 7, 11 + :emphasize-lines: 7, 12 class Person include Mongoid::Document From ca219dcd9ed3ed2ef2f4804b610972ea0002b18c Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 15 Nov 2024 15:42:50 -0500 Subject: [PATCH 075/113] small fixes - MW --- source/interact-data/specify-query.txt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 39c12272..6c706674 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -558,9 +558,10 @@ with the specified ``_id`` field value: .. note:: Type Conversion - The ``find()`` method converts the ID that you pass to the - data type declared for the ``_id`` field in the model. By - default, the ``_id`` field is defined as a ``BSON::ObjectId`` type. + When you pass an ID value to the ``find()`` method, the method + converts it to the data type declared for the ``_id`` field in the + model. By default, the ``_id`` field is defined as a + ``BSON::ObjectId`` type. The preceding example is equivalent to the following code, which passes an ``BSON::ObjectId`` instance as the argument to ``find()``: @@ -640,8 +641,8 @@ method: You can use the ``find_or_initialize_by()`` method to retrieve documents based on the provided criteria. If no documents are found, it returns a -new one, without persisting it to MongoDB. The syntax for -``find_or_initialize_by()`` is the same for the ``find_or_create_by()`` +new one, without persisting it to MongoDB. Use the same syntax for +``find_or_initialize_by()`` as you do for the ``find_or_create_by()`` method. Regular Expressions @@ -785,7 +786,7 @@ result from the list of returned documents based on its position. following code for an example.* - ``first_or_create()``: Returns the first matching document. If no - document matches, creates and returns a newly persisted one. + document matches, creates and returns a newly saved one. - ``first_or_initialize()``: Returns the first matching document. If no document matches, returns a new one. From 644c525a41fb4223a21eda956ced42bb2a812fc6 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 18 Nov 2024 11:22:27 -0500 Subject: [PATCH 076/113] DOCSP-45358: documents --- source/data-modeling.txt | 11 +++--- source/data-modeling/documents.txt | 59 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 source/data-modeling/documents.txt diff --git a/source/data-modeling.txt b/source/data-modeling.txt index 45c78994..1a81e1a8 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -11,11 +11,12 @@ Model Your Data .. meta:: :keywords: ruby framework, odm, model, class, query -.. .. toctree:: -.. :caption: Data Modeling -.. -.. Documents +.. toctree:: + :caption: Data Modeling + + Documents In this section, you can learn how to model data in {+odm+}. -.. - :ref:``: Learn how to ... +- :ref:`mongoid-modeling-documents`: Learn about the ``Document`` object + type. diff --git a/source/data-modeling/documents.txt b/source/data-modeling/documents.txt new file mode 100644 index 00000000..0a3e1f6a --- /dev/null +++ b/source/data-modeling/documents.txt @@ -0,0 +1,59 @@ +.. _mongoid-modeling-documents: + +========= +Documents +========= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, code example, bson + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn about the ``Mongoid::Document`` module in +{+odm+}. The ``Document`` module is a representation of a MongoDB +document. To learn more about the terminology, structure, and limitations of +MongoDB documents, see :manual:`Documents ` in the +{+server-manual+}. + +You must include the ``Mongoid::Document`` module in any class that you +want to persist to MongoDB. By including the ``Document`` module in your +model class, you can use its methods on instances of your model class. + +The following code demonstrates how to include the ``Document`` module +in a sample ``Person`` model class: + +.. code-block:: ruby + :emphasize-lines: 2 + + class Person + include Mongoid::Document + + field :name, type: String + end + +You can find more information about the ``Document`` module in the `API +documentation <{+api-root+}/Document.html>`__. + +MongoDB Representation +---------------------- + +The representation of a ``Document`` in MongoDB is a BSON object that is +similar to a {+language+} hash or JSON object. You can store instances +of your models directly in a collection in the database, or you can +embed them in other classes that use the ``Document`` module. + +To learn more about how to model your data by using {+odm+} models, +see the :ref:`mongoid-data-modeling` guides. + +.. TODO Add link to field types guide. From a474e123966bd1c95d8cb40c04ef06d054ef5605 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 18 Nov 2024 11:23:16 -0500 Subject: [PATCH 077/113] fix --- source/data-modeling.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/data-modeling.txt b/source/data-modeling.txt index 1a81e1a8..88bdc04f 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -18,5 +18,5 @@ Model Your Data In this section, you can learn how to model data in {+odm+}. -- :ref:`mongoid-modeling-documents`: Learn about the ``Document`` object - type. +- :ref:`mongoid-modeling-documents`: Learn about the ``Document`` + module. From 0c5c569b805618e5d4c430d205b62d3e3b4c512d Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 18 Nov 2024 11:42:01 -0500 Subject: [PATCH 078/113] wip --- snooty.toml | 3 ++- source/data-modeling/documents.txt | 33 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/snooty.toml b/snooty.toml index 5b4584c2..04a3ea10 100644 --- a/snooty.toml +++ b/snooty.toml @@ -10,7 +10,8 @@ toc_landing_pages = [ "/quick-start-rails", "/quick-start-sinatra", "/interact-data", - "/interact-data/specify-query" + "/interact-data/specify-query", + "/data-modeling" ] [constants] diff --git a/source/data-modeling/documents.txt b/source/data-modeling/documents.txt index 0a3e1f6a..8503ec3a 100644 --- a/source/data-modeling/documents.txt +++ b/source/data-modeling/documents.txt @@ -53,6 +53,39 @@ similar to a {+language+} hash or JSON object. You can store instances of your models directly in a collection in the database, or you can embed them in other classes that use the ``Document`` module. +The following code creates an instance of the ``Person`` model defined +in the preceding section: + +.. code-block:: ruby + + Person.create(name: 'Meena Kumar') + +The document appears in MongoDB as follows: + +.. code-block:: json + + { + "_id": { + "$oid": "673b6dce61700598c24a72b0" + }, + "name": "Meena Kumar" + } + +.. note:: _id Field + + When you persist an instance of a model to the database, MongoDB + automatically adds an ``_id`` field that has a unique value even if you + do not explicitly define this field in your model. + + To learn more about this field, see the :manual:`ObjectId reference + ` in the {+server-manual+}. + +Additional Information +---------------------- + +To learn how to access and change your MongoDB data, see the +:ref:`mongoid-interact-data` guides. + To learn more about how to model your data by using {+odm+} models, see the :ref:`mongoid-data-modeling` guides. From d2e3f1c4d32a19fafff3a234f82b77ac42b7c2b8 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 18 Nov 2024 11:44:59 -0500 Subject: [PATCH 079/113] wip --- source/data-modeling/documents.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/source/data-modeling/documents.txt b/source/data-modeling/documents.txt index 8503ec3a..a8c9d4a6 100644 --- a/source/data-modeling/documents.txt +++ b/source/data-modeling/documents.txt @@ -63,6 +63,7 @@ in the preceding section: The document appears in MongoDB as follows: .. code-block:: json + :copyable: false { "_id": { From d6eb2210685e35114ece90029d5a90b571eb1451 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 18 Nov 2024 16:45:20 -0500 Subject: [PATCH 080/113] DR tech review 1 --- source/includes/interact-data/transaction.rb | 23 +++---- source/interact-data/transaction.txt | 71 ++++++++++---------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/source/includes/interact-data/transaction.rb b/source/includes/interact-data/transaction.rb index fdfa3025..7848ffc7 100644 --- a/source/includes/interact-data/transaction.rb +++ b/source/includes/interact-data/transaction.rb @@ -63,28 +63,25 @@ class User # start-lower-lvl-api # Starts a session from the model class Book.with_session do |session| - # Starts the transaction in the session - session.start_transaction + session.start_transaction + # Creates a Book + Book.create(title: 'Siddhartha', author: 'Hermann Hesse') + + # Commits the transaction + session.commit_transaction +rescue StandardError + # Ends the transaction if there is an error + session.abort_transaction end book = Book.new -# Starts a session from an instance of Book +# Starts a session from an *instance* of Book book.with_session do |session| # Starts the transaction in the session session.start_transaction end # end-lower-lvl-api -# start-commit-abort -Book.with_session do |session| - session.commit_transaction -end - -Book.with_session do |session| - session.abort_transaction -end -# end-commit-abort - # start-commit-retry begin session.commit_transaction diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index 2c1b39c5..acde5fd4 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -42,21 +42,21 @@ encounter unexpected errors. In {+odm+}, you can perform transactions by using either of the following APIs: -- :ref:`mongoid-txn-convenient`: {+odm+} manages the life cycle of the +- :ref:`mongoid-txn-high-level`: {+odm+} manages the life cycle of the transaction. You can use this API in {+odm+} v9.0 and later. -- :ref:`mongoid-txn-core`: You must manage the life cycle of the +- :ref:`mongoid-txn-low-level`: You must manage the life cycle of the transaction. You can use this API in {+odm+} v6.4 and later. The :ref:`mongoid-txn-session` section describes how to make changes to your data from within a session without performing a transaction. -.. _mongoid-txn-convenient: +.. _mongoid-txn-high-level: -Convenient Transaction API +High-Level Transaction API -------------------------- -You can use the Convenient Transaction API to internally manage the +You can use the High-Level Transaction API to internally manage the lifecycle of your transaction. This API either commits your transaction or ends it and incorporates error handling logic. @@ -170,17 +170,28 @@ unsuccessful and changes were rolled back. {+odm+} never triggers .. TODO link to callbacks guide. -.. _mongoid-txn-core: +.. _mongoid-txn-low-level: -Core Transaction API --------------------- +Low-Level Transaction API +------------------------- -When using the Core API, you must create a session before +When using the low-level API, you must create a session before starting a transaction. You can create a session by calling the ``with_session()`` method on a model class or an instance of a model. Then, you can start a transaction by calling the ``start_transaction()`` -method on a session, as shown in the following code: +method on a session. When using this API, you must manually commit or +end the transaction. You can use the ``commit_transaction()`` and +``abort_transaction()`` methods on the session instance to manage the +transaction lifecycle. + +This example demonstrates how to use the low-level transaction API +to perform the following actions: + +- Create a session +- Start a transaction +- Perform data operations +- Commit the transaction, or end it if there are errors .. literalinclude:: /includes/interact-data/transaction.rb :start-after: start-lower-lvl-api @@ -188,6 +199,21 @@ method on a session, as shown in the following code: :language: ruby :dedent: +.. note:: + + If a session ends and includes an open transaction, the transaction is + automatically ended. + +You can retry the transaction commit if it fails initially. The +following example demonstrates how to retry the transaction when {+odm+} +raises the ``UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL`` exception: + +.. literalinclude:: /includes/interact-data/transaction.rb + :start-after: start-commit-retry + :end-before: end-commit-retry + :language: ruby + :dedent: + You can specify a read concern, write concern or read preference when starting a transaction by passing options to the ``start_transaction()`` method: @@ -205,31 +231,6 @@ To learn more about the available transaction options, see ` in the {+ruby-driver+} API documentation. -When using this API, you must manually commit or end the transaction. -You can use the ``commit_transaction()`` and ``abort_transaction()`` -methods on the session instance to manage the transaction lifecycle: - -.. literalinclude:: /includes/interact-data/transaction.rb - :start-after: start-commit-abort - :end-before: end-commit-abort - :language: ruby - :dedent: - -.. note:: - - If a session ends and includes an open transaction, the transaction is - automatically ended. - -You can retry the transaction commit if it fails initially. The -following example demonstrates how to retry the transaction when {+odm+} -raises the ``UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL`` exception: - -.. literalinclude:: /includes/interact-data/transaction.rb - :start-after: start-commit-retry - :end-before: end-commit-retry - :language: ruby - :dedent: - Client Behavior ~~~~~~~~~~~~~~~ From 24e68c4ff8f8d7efd7ef2fb1568c6d1eab00eebd Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 18 Nov 2024 16:49:45 -0500 Subject: [PATCH 081/113] page fmt --- source/interact-data/transaction.txt | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index acde5fd4..5ff81d7e 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -185,13 +185,16 @@ end the transaction. You can use the ``commit_transaction()`` and ``abort_transaction()`` methods on the session instance to manage the transaction lifecycle. -This example demonstrates how to use the low-level transaction API -to perform the following actions: +Example +~~~~~~~ + +This example uses the low-level transaction API to perform the following +actions: -- Create a session -- Start a transaction -- Perform data operations -- Commit the transaction, or end it if there are errors +1. Creates a session +#. Starts a transaction +#. Performs data operations +#. Commits the transaction, or ends it if there are errors .. literalinclude:: /includes/interact-data/transaction.rb :start-after: start-lower-lvl-api @@ -204,6 +207,9 @@ to perform the following actions: If a session ends and includes an open transaction, the transaction is automatically ended. +Transaction Retry +~~~~~~~~~~~~~~~~~ + You can retry the transaction commit if it fails initially. The following example demonstrates how to retry the transaction when {+odm+} raises the ``UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL`` exception: @@ -214,6 +220,9 @@ raises the ``UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL`` exception: :language: ruby :dedent: +Options +~~~~~~~ + You can specify a read concern, write concern or read preference when starting a transaction by passing options to the ``start_transaction()`` method: From d7478517b61373f109a8edaa06a130d0c06c5011 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 18 Nov 2024 16:51:47 -0500 Subject: [PATCH 082/113] page fmt --- source/includes/interact-data/transaction.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/source/includes/interact-data/transaction.rb b/source/includes/interact-data/transaction.rb index 7848ffc7..ad8a74de 100644 --- a/source/includes/interact-data/transaction.rb +++ b/source/includes/interact-data/transaction.rb @@ -73,13 +73,6 @@ class User # Ends the transaction if there is an error session.abort_transaction end - -book = Book.new -# Starts a session from an *instance* of Book -book.with_session do |session| - # Starts the transaction in the session - session.start_transaction -end # end-lower-lvl-api # start-commit-retry From 19d711813ba14b9845464cf21d116cc16121abb2 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 18 Nov 2024 16:59:35 -0500 Subject: [PATCH 083/113] SA PR fixes 1 --- source/data-modeling/inheritance.txt | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/source/data-modeling/inheritance.txt b/source/data-modeling/inheritance.txt index bc426aa6..26654874 100644 --- a/source/data-modeling/inheritance.txt +++ b/source/data-modeling/inheritance.txt @@ -24,7 +24,7 @@ In this guide, you can learn how to implement **inheritance** into your {+odm+} models. Inheritance allows you to apply the characteristics of one "parent" class to one or more "child" classes. -{+odm+} supports inheritance in top level and embedded documents. +{+odm+} supports inheritance in top-level and embedded documents. When a child model class inherits from a parent class, {+odm+} copies the parent class's fields, associations, validations, and scopes to the child class. @@ -44,11 +44,10 @@ demonstrate how to create parent and child classes between the :emphasize-lines: 7, 14 :dedent: -When you perform data operations by using the preceding models, -instances of ``Person``, ``Employee``, or ``Manager`` are all saved in the -``people`` collection. {+odm+} sets the ``_type`` discriminator field to -the model class name in documents to ensure that documents are returned -as the expected types when you retrieve them. +{+odm+} saves instances of ``Person``, ``Employee``, and ``Manager`` in +the ``people`` collection. {+odm+} sets the ``_type`` discriminator +field to the model class name in documents to ensure that documents are +returned as the expected types when you perform read operations. Embedded Documents ~~~~~~~~~~~~~~~~~~ @@ -73,7 +72,7 @@ Query Behavior ~~~~~~~~~~~~~~ When you query on a child model class, the query returns only documents -in which the value of the ``_type`` field match the queried class or +in which the value of the ``_type`` field matches the queried class or further child classes. For example, if you query on the ``Employee`` class, the query returns documents from the ``people`` collection in which the ``_type`` value is either ``"Employee"`` or ``"Manager"``. All @@ -190,11 +189,13 @@ name. Because the discriminator value customization is declared in child classes, you must load the child classes retrieved by a query *before* sending - that query. In the preceding example, the ``Employee`` class definition - must be loaded before you query on ``Person`` if the returned documents could - potentially be instances of ``Employee``. Autoloading isn't able to resolve - the discriminator value ``"Worker"`` to return the document as an - instance of ``Employee``. + that query. + + In the preceding example, the ``Employee`` class definition must be + loaded before you query on ``Person`` if the returned documents include + instances of ``Employee``. Autoloading cannot resolve the + discriminator value ``"Worker"`` to return documents as instances of + ``Employee``. Embedded Associations --------------------- From b217789a8f7ece6c813a448379cd131c6ce911ed Mon Sep 17 00:00:00 2001 From: rustagir Date: Wed, 20 Nov 2024 09:55:37 -0500 Subject: [PATCH 084/113] MR PR fixes 1 --- source/data-modeling/documents.txt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/source/data-modeling/documents.txt b/source/data-modeling/documents.txt index a8c9d4a6..5fcbea93 100644 --- a/source/data-modeling/documents.txt +++ b/source/data-modeling/documents.txt @@ -21,8 +21,9 @@ Overview -------- In this guide, you can learn about the ``Mongoid::Document`` module in -{+odm+}. The ``Document`` module is a representation of a MongoDB -document. To learn more about the terminology, structure, and limitations of +{+odm+}. The ``Document`` module is a {+language+} implementation of a +MongoDB document, which stores data in field-and-value pairs. To learn +more about the terminology, structure, and limitations of MongoDB documents, see :manual:`Documents ` in the {+server-manual+}. @@ -45,13 +46,14 @@ in a sample ``Person`` model class: You can find more information about the ``Document`` module in the `API documentation <{+api-root+}/Document.html>`__. -MongoDB Representation ----------------------- +Work with Documents +------------------- -The representation of a ``Document`` in MongoDB is a BSON object that is -similar to a {+language+} hash or JSON object. You can store instances -of your models directly in a collection in the database, or you can -embed them in other classes that use the ``Document`` module. +You can store instances of your models directly in a collection, or you +can embed them in other classes that use the ``Document`` module. +When you save a ``Document`` instance to MongoDB, it is converted +to a BSON object that is similar to a {+language+} hash or JSON +object. The following code creates an instance of the ``Person`` model defined in the preceding section: From e0aae730bacc1dd55c2055d8d0a210477c63afd2 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:42:49 -0500 Subject: [PATCH 085/113] DR small fix Co-authored-by: Dmitry Rybakov <160598371+comandeo-mongo@users.noreply.github.com> --- source/interact-data/transaction.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index 5ff81d7e..04cef370 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -152,7 +152,7 @@ created, saved, or deleted in the following cases: - After the transaction is committed if the object was modified inside the transaction. -- After the object is persisted if the object was modified before +- After the object is persisted if the object was modified outside the transaction block. The ``after_commit`` callback is triggered only after all From 895424a49588717465e86775c57a68ef8527ee55 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:58:01 -0500 Subject: [PATCH 086/113] DOCSP-45360: nested attributes (#71) * DOCSP-45360: nested attributes * vale + fixes * fixes * NR PR fixes 1 --- source/data-modeling.txt | 4 + source/data-modeling/nested-attributes.txt | 192 +++++++++++++++++++ source/includes/data-modeling/nested_attr.rb | 58 ++++++ 3 files changed, 254 insertions(+) create mode 100644 source/data-modeling/nested-attributes.txt create mode 100644 source/includes/data-modeling/nested_attr.rb diff --git a/source/data-modeling.txt b/source/data-modeling.txt index 829f6643..8c5d324c 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -16,6 +16,7 @@ Model Your Data Documents Inheritance + Nested Attributes In this section, you can learn how to model data in {+odm+}. @@ -24,3 +25,6 @@ In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-modeling-inheritance`: Learn how to implement inheritance in your model classes. + +- :ref:`mongoid-modeling-nested-attr`: Learn how to modify documents and + their associations in a single operation. diff --git a/source/data-modeling/nested-attributes.txt b/source/data-modeling/nested-attributes.txt new file mode 100644 index 00000000..7e8296d2 --- /dev/null +++ b/source/data-modeling/nested-attributes.txt @@ -0,0 +1,192 @@ +.. _mongoid-modeling-nested-attr: + +================= +Nested Attributes +================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, embeddings, code example, queries + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to define **nested attributes** on +models to enable data operations on documents and their associations. +After you define a nested attribute, you can specify updates to +top-level and associated documents in a single parameter hash. This might be +useful if your application requires editing multiple documents within a single +form. + +Behavior +-------- + +You can enable nested attributes for any association, embedded or +referenced. To add a nested attribute for an association, provide the +association name to the ``accepts_nested_attributes_for`` macro when +defining a model class. + +The following code defines embedded associations on the ``Band`` model +class and includes the ``accepts_nested_attributes_for`` macro: + +.. literalinclude:: /includes/data-modeling/nested_attr.rb + :start-after: start-simple-nested + :end-before: end-simple-nested + :language: ruby + :emphasize-lines: 5 + :dedent: + +.. note:: Autosave Enabled + + When you add nested attribute functionality to a referenced + association, {+odm+} automatically enables autosave for that + association. + +When you enable nested attributes behavior on an association, {+odm+} +adds a special method to the base model. You can use this method to +update the attributes. + +The method name is the association name suffixed with ``_attributes``. For +example, the setter method to update the ``producers`` association is +``producer_attributes``. + +You can use this method directly, or you can use the name of the method +as an attribute in the updates for the top-level class. In this case, +{+odm+} calls the appropriate setter method internally. + +The following code retrieves an instance of ``Band``, then uses the +nested attribute update method ``producer_attributes`` to set a value +for the association document: + +.. literalinclude:: /includes/data-modeling/nested_attr.rb + :start-after: start-use-method + :end-before: end-use-method + :language: ruby + :emphasize-lines: 4 + :dedent: + +There are multiple ways to update a nested attribute: + +- Use the ``_attributes`` setter method. +- Use the ``attributes`` setter method and specify ``_attributes`` in the value to update the associations. +- Use the ``update_attributes`` setter method and specify the attribute + names in the value to update the associations. +- Use the ``update()`` method and specify ``_attributes`` in the value to update the associations. +- Use the ``create()`` method and specify ``_attributes`` in the value to create the associations. + +The following example demonstrates how to create a ``Band`` instance +with associated ``album`` records in a single statement: + +.. literalinclude:: /includes/data-modeling/nested_attr.rb + :start-after: start-create-attr + :end-before: end-create-attr + :language: ruby + :emphasize-lines: 3-5 + :dedent: + +Creating Nested Documents +------------------------- + +You can create new nested documents by using the nested attributes +feature. When creating a document, omit the ``_id`` field. The following +code uses the ``update()`` method to create a nested ``album`` document +on an existing ``Band`` instance: + +.. literalinclude:: /includes/data-modeling/nested_attr.rb + :start-after: start-update-create + :end-before: end-update-create + :language: ruby + :dedent: + +This action appends the new document to the existing set without changing +any existing nested documents. + +Updating Nested Documents +------------------------- + +You can update existing nested documents by using the nested attributes +feature. To instruct {+odm+} to update a nested document by using +attributes, pass the document's ``_id`` value to the ``update()`` +method. The following example uses the ``_id`` value of an ``albums`` +entry to update the ``year`` field: + +.. literalinclude:: /includes/data-modeling/nested_attr.rb + :start-after: start-update-id + :end-before: end-update-id + :language: ruby + :dedent: + +.. important:: No Matching Document + + If {+odm+} does not match a document that has the specified ``_id`` + value, it raises a ``Mongoid::Errors::DocumentNotFound`` exception. + +Delete Nested Documents +----------------------- + +You can delete nested documents by specifying the ``_destroy`` +attribute to the ``update()`` method. To enable deletion of nested +document, you must set ``allow_destroy: true`` in the +``accepts_nested_attributes_for`` declaration, as shown in the following +code: + +.. code-block:: ruby + :emphasize-lines: 3 + + class Band + # ... + accepts_nested_attributes_for :albums, allow_destroy: true + end + +The following code uses the ``_destroy`` attribute to delete the first +``albums`` entry of a ``Band`` instance: + +.. literalinclude:: /includes/data-modeling/nested_attr.rb + :start-after: start-delete-id + :end-before: end-delete-id + :language: ruby + :emphasize-lines: 6 + :dedent: + +.. important:: No Matching Document + + If {+odm+} does not match a document that has the specified ``_id`` + value, it raises a ``Mongoid::Errors::DocumentNotFound`` exception. + +Combine Operations on Nested Documents +-------------------------------------- + +You can perform multiple data operations on nested documents by using +the nested attributes feature. + +The following code creates a nested document, updates an existing +document, and deletes a document in the ``albums`` array of a ``Band`` +instance: + +.. literalinclude:: /includes/data-modeling/nested_attr.rb + :start-after: start-multiple-ops + :end-before: end-multiple-ops + :language: ruby + :dedent: + +Additional Information +---------------------- + +To learn more about querying, see the :ref:`mongoid-data-specify-query` +guide. + +.. TODO link to CRUD guide + +.. TODO link to associations guide diff --git a/source/includes/data-modeling/nested_attr.rb b/source/includes/data-modeling/nested_attr.rb new file mode 100644 index 00000000..e81a4c4d --- /dev/null +++ b/source/includes/data-modeling/nested_attr.rb @@ -0,0 +1,58 @@ +# start-simple-nested +class Band + include Mongoid::Document + embeds_many :albums + belongs_to :producer + accepts_nested_attributes_for :albums, :producer +end +# end-simple-nested + +#start-use-method +# Retrieves a Band instance +band = Band.where(name: 'Tennis').first +# Updates the "producer" association +band.producer_attributes = { name: 'Alaina Moore' } +#end-use-method + +# start-create-attr +band = Band.create( + name: 'Tennis', + albums_attributes: [ + { name: 'Swimmer', year: 2020 }, + { name: 'Young & Old', year: 2013 }] +) +# end-create-attr + +# start-update-create +band = Band.where(name: 'Vampire Weekend').first +band.update(albums_attributes: [ + { name: 'Contra', year: 2010 } +]) +# end-update-create + +# start-update-id +band = Band.where(name: 'Vampire Weekend').first +# Retrieves the first entry from the albums array +album = band.albums.first +# Updates the entry by passing the _id value +band.update(albums_attributes: [ + { _id: album._id, year: 2011 } ]) +# end-update-id + +# start-delete-id +band = Band.where(name: 'Vampire Weekend').first +# Retrieves the first entry from the albums array +album = band.albums.first +# Deletes the entry by passing the _id value +band.update(albums_attributes: [ + { _id: album._id, _destroy: true } ]) +# end-delete-id + +# start-multiple-ops +band = Band.where(name: 'Yeah Yeah Yeahs').first +# Performs multiple data changes +band.update(albums_attributes: [ + { name: 'Show Your Bones', year: 2006 }, + { _id: 1, name: 'Fever To T3ll' }, + { _id: 2, _destroy: true } ]) +# end-multiple-ops \ No newline at end of file From 25fd4bdad56f67263678fd173ad3082a918163b9 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:48:25 -0500 Subject: [PATCH 087/113] DOCSP-45362: text search (#72) * DOCSP-45362: text search * wip * vale * MM PR fixes 1 --- source/includes/interact-data/text-search.rb | 22 ++ source/interact-data.txt | 4 + source/interact-data/text-search.txt | 213 +++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 source/includes/interact-data/text-search.rb create mode 100644 source/interact-data/text-search.txt diff --git a/source/includes/interact-data/text-search.rb b/source/includes/interact-data/text-search.rb new file mode 100644 index 00000000..1d431ceb --- /dev/null +++ b/source/includes/interact-data/text-search.rb @@ -0,0 +1,22 @@ +# start-text-index-model +class Dish + include Mongoid::Document + + field :name, type: String + field :description, type: String + + index description: 'text' +end +# end-text-index-model + +# start-term +Dish.where('$text' => {'$search' => 'herb'}) +# end-term + +# start-phrase +Dish.where('$text' => {'$search' => "\"serves 2\""}) +# end-phrase + +# start-exclude +Dish.where('$text' => {'$search' => 'vegan -tofu'}) +# end-exclude \ No newline at end of file diff --git a/source/interact-data.txt b/source/interact-data.txt index ec2ad6b3..b11a2b05 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -16,6 +16,7 @@ Interact with Data Specify a Query Modify Query Results + Search Text Transactions and Sessions In this section, you can learn how to use {+odm+} to interact with your @@ -27,5 +28,8 @@ MongoDB data. - :ref:`mongoid-data-modify-results`: Learn how to modify the way that {+odm+} returns results from queries. +- :ref:`mongoid-data-text-search`: Learn how to perform efficient + searches on text fields. + - :ref:`mongoid-data-txn`: Learn how to perform multi-document transactions to make atomic data changes. \ No newline at end of file diff --git a/source/interact-data/text-search.txt b/source/interact-data/text-search.txt new file mode 100644 index 00000000..a777bf0d --- /dev/null +++ b/source/interact-data/text-search.txt @@ -0,0 +1,213 @@ +.. _mongoid-data-text-search: + +=========== +Search Text +=========== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, crud, filter, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use {+odm+} to run a **text +search**. A text search allows you to efficiently query fields that have +string values. + +MongoDB provides text indexes to support text search queries on +fields that have string values or values that are arrays of string +elements. To learn more about text indexes, see :manual:`Text Indexes on +Self-Managed Deployments ` in the +{+server-manual+}. + +.. note:: Atlas Search + + This guide focuses on text search. If your database is hosted on + MongoDB Atlas, you can use the Atlas Search feature + to perform more powerful and flexible text searches. To learn more + about Atlas Search, see the :atlas:`Atlas Search Overview + ` in the Atlas documentation. + +You can run a text search by performing the following steps: + +1. Define a text index on a model. +#. Create the text index on the target collection. +#. Perform a text search query. + +The following sections describe how to perform each of these actions. + +Define a Text Index on Your Model +--------------------------------- + +.. TODO link to indexes page + +Use the ``index`` macro to specify the text index in your model +definition. The following code creates a ``Dish`` model class that +includes a text index on the ``description`` field: + +.. literalinclude:: /includes/interact-data/text-search.rb + :start-after: start-text-index-model + :end-before: end-text-index-model + :language: ruby + :emphasize-lines: 7 + :dedent: + +.. note:: + + You must specify the index type as a string, as shown by ``'text'`` + in the preceding code. + +Create the Text Index +--------------------- + +Next, you must create the text index in your collection. You can +create the index by using an interface such as the :atlas:`Atlas UI +` or :compass:`Compass `. If you are using +the Rails framework to develop your application, you can run the following +Rake task to create the index based on your model specification: + +.. code-block:: bash + + bundle exec rake db:mongoid:create_indexes + +Perform Text Searches +--------------------- + +To perform a text search, use the ``$text`` evaluation query operator, +followed by the ``$search`` field in your query filter. The ``$text`` operator +performs a text search on the text indexed fields. The ``$search`` field +specifies the text to search in the text indexed fields. To learn more +about this operator, see the :manual:`$text reference +` in the {+server-manual+}. + +.. _mongoid-term-search: + +Search by a Term +~~~~~~~~~~~~~~~~ + +To search for a term, specify the term as a string in your query filter. +To search for multiple terms, separate each term with spaces in the string. + +.. note:: Searching for Multiple Terms + + When searching for multiple terms, {+odm+} returns + documents with at least one of the terms in text indexed fields. + + Suppose you search by using the string ``'cake coffee cream'``. The + following list describes values that match this text query: + + - ``'Has strong coffee notes.'`` + - ``'Good for creamy coffee fans.'`` + - ``'A rich but light cake.'`` + - ``'A creamy coffee cake with cranberries.'`` + +The following example runs a text search for ``description`` values that contain +the term ``'herb'``: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/interact-data/text-search.rb + :start-after: start-term + :end-before: end-term + :language: rust + :dedent: + + .. output:: + :language: none + :visible: false + + # Sample output + {"_id":"...","description":"A bright, herb-based salad. A perfect starter for vegetarians and vegans.","name":"Kale Tabbouleh"} + {"_id":"...","description":"Grilled whole fish stuffed with herbs and pomegranate seeds. Serves 3-4.","name":"Herbed Whole Branzino"} + +.. tip:: + + Although the search term was ``'herb'``, the method also matches + descriptions containing ``'herbs'`` because a MongoDB text index uses *suffix + stemming* to match similar words. To learn more about how + MongoDB matches terms, see :manual:`Text Index Properties + ` in the + {+server-manual}. + +Search by a Phrase +~~~~~~~~~~~~~~~~~~ + +To search for a phrase, specify the phrase with escaped quotes as a +string in your query filter. If you don't add escaped quotes around the +phrase, {+odm+} runs a :ref:`term search `. + +.. tip:: + + Escaped quotes are a backslash character (``\``) followed by a double + quote character (``"``). + +The following example runs a text search for ``description`` values that +contain the phrase ``"serves 2"``: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/interact-data/text-search.rb + :start-after: start-phrase + :end-before: end-phrase + :language: rust + :dedent: + + .. output:: + :language: none + :visible: false + + # Sample output + {"_id":"...","description":"A vegetarian take on the classic dish that uses lentils as a base. Serves 2.","name":"Shepherd’s Pie"} + {"_id":"...","description":"Baked trout seasoned with garlic, lemon, dill, and, of course, butter. Serves 2.","name":"Garlic Butter Trout"} + +Search with Excluded Terms +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For each term or phrase to exclude from your text search, +specify the term or phrase prefixed with a minus sign (``-``) as a string in +your query filter. + +.. important:: + + You must search for at least one term to exclude + terms from your search. If you don't search for any terms, {+odm+} + doesn't return any documents. + +The following example runs a text search for ``description`` values that +contain the term ``'vegan'``, but do not contain the term ``'tofu'``: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/interact-data/text-search.rb + :start-after: start-exclude + :end-before: end-exclude + :language: rust + :dedent: + + .. output:: + :language: none + :visible: false + + # Sample output + {"_id":"...","description":"A bright, herb-based salad. A perfect starter for vegetarians and vegans.","name":"Kale Tabbouleh"} + +Additional Information +---------------------- + +To learn more about constructing query filters, see +:ref:`mongoid-data-specify-query`. + +.. TODO link to CRUD guide From a436575785a07678dcb71c6c5932a89162879c1e Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:09:41 -0800 Subject: [PATCH 088/113] DOCSP-45436 Field Behaviors page (#68) --- source/data-modeling.txt | 10 +- source/data-modeling/field-behaviors.txt | 292 ++++++++++++++++++ .../includes/data-modeling/field-behaviors.rb | 139 +++++++++ 3 files changed, 438 insertions(+), 3 deletions(-) create mode 100644 source/data-modeling/field-behaviors.txt create mode 100644 source/includes/data-modeling/field-behaviors.rb diff --git a/source/data-modeling.txt b/source/data-modeling.txt index 8c5d324c..ba6be34e 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -13,15 +13,19 @@ Model Your Data .. toctree:: :caption: Data Modeling - + +In this section, you can learn how to model data in {+odm+}. + Documents + Field Behaviors Inheritance Nested Attributes -In this section, you can learn how to model data in {+odm+}. - - :ref:`mongoid-modeling-documents`: Learn about the ``Document`` module. + +- :ref:`mongoid-field-behaviors`: Learn how to customize the behaviors of fields + in {+odm+} to meet your application requirements. - :ref:`mongoid-modeling-inheritance`: Learn how to implement inheritance in your model classes. diff --git a/source/data-modeling/field-behaviors.txt b/source/data-modeling/field-behaviors.txt new file mode 100644 index 00000000..daf9dc23 --- /dev/null +++ b/source/data-modeling/field-behaviors.txt @@ -0,0 +1,292 @@ +.. _mongoid-field-behaviors: + +========================= +Customize Field Behaviors +========================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: customize, attributes, optimize, model, configure, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to customize the behavior of fields in {+odm+} models. + +Specify Default Values +---------------------- + +You can configure fields to have default values by using the ``default`` option. +Default field values can be either fixed or ``Proc`` values. + +The following example specifies a fixed default value for the ``state`` +field: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-field-default + :end-before: # end-field-default + +The following example specifies a ``Proc`` default value for the ``fulfill_by`` +field: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-field-default-processed + :end-before: # end-field-default-processed + +.. note:: + + The driver evaluates default values that are not ``Proc`` instances when the + class *loads*. The driver evaluates ``Proc`` values when the document is + *instantiated*. The following default field values do not produce equivalent outcomes: + + .. code-block:: ruby + + # Time.now is set to the time the class is loaded + field :submitted_at, type: Time, default: Time.now + + # Time.now is set to the time the document is instantiated + field :submitted_at, type: Time, default: ->{ Time.now } + +You can set a default value that depends on the document's state by using the +``self`` keyword in a ``Proc`` instance. The following example sets the +``fulfill_by`` default value to depend on the state of the ``submitted_at`` +field: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-field-default-self + :end-before: # end-field-default-self + +By default, {+odm+} applies ``Proc`` default values after setting and +initializing all other attributes. To apply the default before setting the other +attributes, set the ``pre_processed`` option to ``true``, as shown in the +following example: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-field-default-pre-processed + :end-before: # end-field-default-pre-processed + +.. tip:: + + Always set the ``pre-processed`` option to ``true`` to set a + default ``Proc`` value for the ``_id`` field. + +Specify Storage Names +--------------------- + +You can specify a separate field name to store in the database, while still +referring to the field by its original name in your application. This can save +storage space, because MongoDB stores all field information along +with every document. + +You can set an alternate storage name by using the ``as:`` keyword. The +following example creates a field called ``name`` that {+odm+} stores in the database as +``n``: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-field-as + :end-before: # end-field-as + +{+odm+} stores the ``name`` field as ``"n"``, but you can still access the field as +``name`` in your application. + +Field Aliases +------------- + +You can create an alias for your field by using the ``alias_attribute`` option. +Specifying an alias does not change how {+odm+} stores the field in the +database, but it allows you to access the field by a different name in your +application. + +The following example specifies an alias for the ``name`` field: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-field-alias + :end-before: # end-field-alias + +To remove a field alias, you can use the ``unalias_attribute`` option. The +following example removes the alias for the ``name`` field: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-field-unalias + :end-before: # end-field-unalias + +You can also use ``unalias_attribute`` to remove the predefined ``id`` alias from the +``_id`` field. This can be used to store different values in the ``_id`` field +and an ``id`` field. + +Field Redefinition +------------------ + +By default, {+odm+} allows you to redefine fields on a model. To raise an error +when a field is redefined, set the ``duplicate_fields_exception`` configuration +option in your ``mongoid.yml`` file to ``true``. + +If the ``duplicate_fields_exception`` option is set to ``true``, you can still +redefine a specific field by setting the ``overwrite`` option to ``true`` when +you define the field. The following example defines the ``name`` field, and then +redefines the field by using the ``overwrite`` option: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-field-overwrite + :end-before: # end-field-overwrite + +Custom ID Field +--------------- + +By default, {+odm+} defines the ``_id`` field on documents to contain a +``BSON::ObjectId`` value that {+odm+} generates automatically. You can customize +the type or specify the default value of the ``_id`` field by specifying it in your +model. + +The following example creates a ``Band`` class with a custom ``_id`` field: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-custom-id + :end-before: # end-custom-id + +You can omit the default value for the ``_id`` field. If you don't specify a +default value for the field, {+odm+} persists the document without an ``_id`` +value. For top-level documents, the MongoDB server automatically +assigns an ``_id`` value. However, for embedded documents, the server does not +assign an ``_id`` value. + +When you don't specify a value for the ``_id`` field, {+odm+} does not retrieve +the automatically assigned value from the server. Because of this, you cannot +retrieve the document from the database by using the ``_id`` value. + +Uncastable Values +----------------- + +A value is considered **uncastable** if it cannot be converted to the specified +field type. For example, an array is considered uncastable when assigned to an +``Integer`` field. + +In v8.0 and later, {+odm+} assigns ``nil`` to values that are uncastable. The +original uncastable value is stored in the ``attributes_before_type_cast`` hash +with their field names. + +Custom Getters and Setters +-------------------------- + +You can override the default getter and setter methods for a field by specifying +a method with the same name as the field and calling the ``read_attribute`` or +``write_attribute`` method to operate on the raw attribute value. + +The following example creates a custom getter and setter for the ``name`` field +of a ``Person`` class: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-custom-getter-setter + :end-before: # end-custom-getter-setter + +Read-Only Attributes +-------------------- + +You can specify a field to be read-only by specifying the ``attr_readonly`` option. +This allows you to create documents with the attributes, but not update them. + +The following example creates a ``Band`` class and specifies the ``name`` field +as read-only: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-read-only + :end-before: # end-read-only + +If you call a mass-update method, such as ``update_attributes``, and pass in a +read-only field, {+odm+} ignores the read-only field and updates all others. If +you attempt to explicitly update a read-only field, {+odm+} raises a +``ReadonlyAttribute`` exception. + +.. note:: + + Calls to atomic persistence operators, such as ``bit`` and ``inc``, still + persist changes to the read-only field. + +Localize Fields +--------------- + +{+odm+} supports localized fields by using the `i18n gem +`__. When you localize a field, {+odm+} +stores the field as a hash of locale keys and values. Accessing the fields +behaves in the same way as a string value. You can localize fields of any field +type. + +The following example creates a ``Product`` class with a localized ``review`` +field: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-localized-field + :end-before: # end-localized-field + +You can get and set all translations at once by calling the ``_translations`` +method: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-localized-translations + :end-before: # end-localized-translations + +You can specify fallbacks for localized fields by enabling the `i18n fallbacks +`__ feature. + +Enable fallbacks in a Rails application by setting the ``config.i18n.fallbacks`` +configuration setting in your environment and setting the fallback languages: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-localized-fallbacks + :end-before: # end-localized-fallbacks + +Enable fallbacks in non-Rails applications by including the module into the i18n +backend and setting the fallback languages: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-localized-fallbacks-non-rails + :end-before: # end-localized-fallbacks-non-rails + +After enabling fallbacks, if an active language does not have have a +translation, it is looked up in the specified fallback language. + +You can disable fallback languages for a specified field by setting the +``fallbacks`` option to false when defining the field: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-localized-no-fallbacks + :end-before: # end-localized-no-fallbacks + +When querying localized fields, {+odm+} automatically alters the query criteria +to match the current locale. The following example queries the ``Product`` class +for a review in the ``en`` locale: + +.. literalinclude:: /includes/data-modeling/field-behaviors.rb + :language: ruby + :start-after: # start-localized-query + :end-before: # end-localized-query + +.. note:: + + If you want to query extensively on localized fields, we recommend indexing + each locale that you want to query on. diff --git a/source/includes/data-modeling/field-behaviors.rb b/source/includes/data-modeling/field-behaviors.rb new file mode 100644 index 00000000..94e9d4dd --- /dev/null +++ b/source/includes/data-modeling/field-behaviors.rb @@ -0,0 +1,139 @@ +# start-field-default +class Order + include Mongoid::Document + + field :state, type: String, default: 'created' +end +# end-field-default + +# start-field-default-processed +class Order + include Mongoid::Document + + field :fulfill_by, type: Time, default: ->{ Time.now + 3.days } +end +# end-field-default-processed + +# start-field-default-self +field :fulfill_by, type: Time, default: ->{ + self.submitted_at + 4.hours +} +# end-field-default-self + +# start-field-default-pre-processed +field :fulfill_by, type: Time, default: ->{ Time.now + 3.days }, + pre_processed: true +# end-field-default-pre-processed + +# start-field-as +class Band + include Mongoid::Document + field :n, as: :name, type: String +end +# end-field-as + +# start-field-alias +class Band + include Mongoid::Document + field :name, type: String + alias_attribute :n, :name +end +# end-field-alias + +# start-field-unalias +class Band + unalias_attribute :n +end +# end-field-unalias + +# start-field-overwrite +class Person + include Mongoid::Document + field :name + field :name, type: String, overwrite: true +end +# end-field-overwrite + +# start-custom-id +class Band + include Mongoid::Document + field :name, type: String + field :_id, type: String, default: ->{ name } +end +# end-custom-id + +# start-custom-getter-setter +class Person + include Mongoid::Document + field :name, type: String + + # Custom getter for 'name' to return the name in uppercase + def name + read_attribute(:name).upcase if read_attribute(:name) + end + + # Custom setter for 'name' to store the name in lowercase + def name=(value) + write_attribute(:name, value.downcase) + end + end +# end-custom-getter-setter + +# start-localized-field +class Product + include Mongoid::Document + field :review, type: String, localize: true +end + +I18n.default_locale = :en +product = Product.new +product.review = "Marvelous!" +I18n.locale = :de +product.review = "Fantastisch!" + +product.attributes +# Outputs: { "review" => { "en" => "Marvelous!", "de" => "Fantastisch!" } +# end-localized-field + +# start-localized-translations +product.review_translations +# Outputs: { "en" => "Marvelous!", "de" => "Fantastisch!" } +product.review_translations = + { "en" => "Marvelous!", "de" => "Wunderbar!" } +# end-localized-translations + +# start-localized-fallbacks +config.i18n.fallbacks = true +config.after_initialize do + I18n.fallbacks[:de] = [ :en, :es ] +end +# end-localized-fallbacks + +# start-localized-fallbacks-non-rails +require "i18n/backend/fallbacks" +I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) +I18n.fallbacks[:de] = [ :en, :es ] +# end-localized-fallbacks-non-rails + +# start-localized-no-fallbacks +class Product + include Mongoid::Document + field :review, type: String, localize: true, fallbacks: false +end +# end-localized-no-fallbacks + +# start-localized-query +# Match all products with Marvelous as the review. The current locale is :en. +Product.where(review: "Marvelous!") +# The resulting MongoDB query filter: { "review.en" : "Marvelous!" } +# end-localized-query + +# start-read-only +class Band + include Mongoid::Document + field :name, type: String + field :origin, type: String + + attr_readonly :name +end +# end-read-only \ No newline at end of file From 4003671a48ee78bba198fda95777ffd0cc27ec34 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:52:37 -0500 Subject: [PATCH 089/113] DOCSP-45363: validation (#73) * DOCSP-45363: validation * keywords * wip * SA PR fixes 1 --- snooty.toml | 1 + source/data-modeling.txt | 12 +- source/data-modeling/validation.txt | 343 ++++++++++++++++++ source/includes/data-modeling/validation.rb | 89 +++++ source/interact-data.txt | 6 +- .../nested-attributes.txt | 2 +- 6 files changed, 445 insertions(+), 8 deletions(-) create mode 100644 source/data-modeling/validation.txt create mode 100644 source/includes/data-modeling/validation.rb rename source/{data-modeling => interact-data}/nested-attributes.txt (99%) diff --git a/snooty.toml b/snooty.toml index b4c9f5f9..789f516a 100644 --- a/snooty.toml +++ b/snooty.toml @@ -17,6 +17,7 @@ toc_landing_pages = [ [constants] rails-6-version = 6.0 rails-7-version = 7.1 +rails-8-version-docs = "v8.0" odm = "Mongoid" version = "9.0" full-version = "{+version+}.2" diff --git a/source/data-modeling.txt b/source/data-modeling.txt index ba6be34e..50af17cf 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -13,13 +13,13 @@ Model Your Data .. toctree:: :caption: Data Modeling - -In this section, you can learn how to model data in {+odm+}. - + Documents Field Behaviors Inheritance - Nested Attributes + Document Validation + +In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-modeling-documents`: Learn about the ``Document`` module. @@ -30,5 +30,5 @@ In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-modeling-inheritance`: Learn how to implement inheritance in your model classes. -- :ref:`mongoid-modeling-nested-attr`: Learn how to modify documents and - their associations in a single operation. +- :ref:`mongoid-modeling-validation`: Learn how to create document + validation rules for your model classes. diff --git a/source/data-modeling/validation.txt b/source/data-modeling/validation.txt new file mode 100644 index 00000000..9f96c72f --- /dev/null +++ b/source/data-modeling/validation.txt @@ -0,0 +1,343 @@ +.. _mongoid-modeling-validation: + +=================== +Document Validation +=================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, schema, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to define **validation rules** in your +{+odm+} models. After you implement validation into your models, {+odm+} +prevents you from running write operations that violate the validation +rules. Use document validation to restrict data types and value ranges +of document fields in your collections. + +{+odm+} includes ``ActiveModel::Validations`` from Active Record to +provide validation functionality, including an associated and uniqueness +validator. To learn more, see the `Active Record Validations +`__ +Rails guide and `ActiveModel::Validations +`__ +Rails API documentation. + +.. note:: Comparing {+odm+} and MongoDB Validation + + Validation in {+odm+} applies only in the context of your + application and differs from creating schema validation rules in + MongoDB. This means that your validation rules do not apply to write + operations that are performed outside of your application. To learn + more about MongoDB schema validation, see :manual:`Schema Validation + ` in the {+server-manual+}. + +Validation Helpers +------------------ + +{+odm+} supports Active Record validation helpers that you can use when defining your +model classes. You can use these helpers to set common validation rules +in your application, such as checking for the presence of a field, +comparing a field value to a specified value, or ensuring that a field +has a unique value. + +Define a Validation Rule +~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``validates`` macro to create a validation rule, then include +the validation helper and the required specifications for the rule. + +.. tip:: + + Each validation helper accepts one or more field names, which allows you + to define the same rule for multiple fields. + +The following code demonstrates how to use the ``presence`` validation +helper to require that ``Person`` instances contain a value for the +``name`` field: + +.. literalinclude:: /includes/data-modeling/validation.rb + :start-after: start-simple-val + :end-before: end-simple-val + :language: ruby + :emphasize-lines: 5 + :dedent: + +You can learn about other useful validation helpers in the +:ref:`mongoid-common-validations` section of this guide. + +.. _mongoid-common-validations: + +Common Validations +------------------ + +In this section, you can learn about the following common validation +rules and view examples that use validation helpers: + +- :ref:`mongoid-compare-validation` +- :ref:`mongoid-format-validation` +- :ref:`mongoid-inclusion-exclusion-validation` +- :ref:`mongoid-presence-absence-validation` +- :ref:`mongoid-uniqueness-validation` +- :ref:`mongoid-association-validation` +- :ref:`mongoid-other-validation` + +.. _mongoid-compare-validation: + +Comparison Rule +~~~~~~~~~~~~~~~ + +You can use the ``comparison`` helper to validate a document based on +the value of a specified field. + +The ``comparison`` helper supports the following options: + +- ``greater_than``: The value must be greater than the supplied value. +- ``greater_than_or_equal_to``: The value must be greater than or equal to the supplied value. +- ``equal_to``: The value must be equal to the supplied value. +- ``less_than``: The value must be less than the supplied value. +- ``less_than_or_equal_to``: The value must be less than or equal to the supplied value. +- ``other_than``: The value must be different than the supplied value. + +This example defines the following comparison validation rules on the +``Order`` model: + +- ``delivery_date``: Must be after (greater than) the value of ``order_date`` +- ``quantity``: Must be less than ``5`` + +.. literalinclude:: /includes/data-modeling/validation.rb + :start-after: start-comparison + :end-before: end-comparison + :language: ruby + :emphasize-lines: 8-9 + :dedent: + +.. _mongoid-format-validation: + +Formatting Rule +~~~~~~~~~~~~~~~ + +You can use the ``format`` helper to validate a document based on +whether a field value matches a regular expression. Use the ``with`` +option to specify the regular expression. + +This example defines a format validation rule on the +``User`` model to ensure that the ``username`` field contains only +letters: + +.. literalinclude:: /includes/data-modeling/validation.rb + :start-after: start-fmt + :end-before: end-fmt + :language: ruby + :emphasize-lines: 6 + :dedent: + +.. tip:: Alternative Helper Method + + The ``Mongoid::Document`` module provides macro methods for certain + validations. Instead of using the ``format`` validation helper in the + ``validates`` macro statement, you can use the + ``validates_format_of`` method, as shown in the following code: + + .. code-block:: ruby + + validates_format_of :username, with: /\A[a-zA-Z]+\z/ + +.. _mongoid-inclusion-exclusion-validation: + +Inclusion or Exclusion Rule +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``inclusion`` and ``exclusion`` helpers to validate a +document based on whether a field value is in a specified list +of values. Use the ``in`` option to specify the list of values. + +This example defines an inclusion validation rule on the +``Order`` model to ensure that the ``shipping`` field value is one of +the accepted values: + +.. literalinclude:: /includes/data-modeling/validation.rb + :start-after: start-inclusion + :end-before: end-inclusion + :language: ruby + :emphasize-lines: 6 + :dedent: + +.. _mongoid-presence-absence-validation: + +Presence or Absence Rule +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``presence`` and ``absence`` helpers to validate a +document based on whether a field value is present or absent (empty). + +This example defines an absence validation rule on the +``Order`` model to ensure that the ``delivery_date`` field value is +either ``nil`` or an empty string: + +.. literalinclude:: /includes/data-modeling/validation.rb + :start-after: start-absence + :end-before: end-absence + :language: ruby + :emphasize-lines: 6 + :dedent: + +.. tip:: Alternative Helper Method + + The ``Mongoid::Document`` module provides macro methods for certain + validations. Instead of using the ``presence`` validation helper in the + ``validates`` macro statement, you can use the + ``validates_presence_of`` method, as shown in the following code: + + .. code-block:: ruby + + validates_presence_of :delivery_date + +.. _mongoid-uniqueness-validation: + +Uniqueness Rule +~~~~~~~~~~~~~~~ + +You can use the ``uniqueness`` helper to validate a +document based on whether a field value is unique from other values in +the collection. You can use the ``scope`` option to specify one or more +field names that {+odm+} uses to limit the uniqueness check. + +This example defines a uniqueness validation rule on the +``Person`` model to ensure that the ``first_name`` field value is +unique within documents that have the same ``last_name`` value: + +.. literalinclude:: /includes/data-modeling/validation.rb + :start-after: start-unique + :end-before: end-unique + :language: ruby + :emphasize-lines: 7 + :dedent: + +.. tip:: Alternative Helper Method + + The ``Mongoid::Document`` module provides macro methods for certain + validations. Instead of using the ``uniqueness`` validation helper in the + ``validates`` macro statement, you can use the + ``validates_uniqueness_of`` method, as shown in the following code: + + .. code-block:: ruby + + validates_uniqueness_of :first_name + + {+odm+} uses a ``primary`` read preference when you use the + ``validates_uniqueness_of`` method on a model, because if it + queries a secondary member of the replica set, it might read stale data. + + This method takes a ``conditions`` option that allows you to specify + conditions to add when {+odm+} checks for uniqueness: + + .. code-block:: ruby + + validates_uniqueness_of :name, conditions: -> { where(:age.gte => 10) } + +.. _mongoid-association-validation: + +Validate Associations +~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``validates_associated`` helper to validate any +associations that your model has. When you include this validation rule, +{+odm+} validates any association documents any time you try to +save an instance. + +.. TODO link to associations page + +This example defines an association validation rule on the +``Author`` model to run the validation rules for the embedded ``Book`` +instances: + +.. literalinclude:: /includes/data-modeling/validation.rb + :start-after: start-assoc + :end-before: end-assoc + :language: ruby + :emphasize-lines: 6 + :dedent: + +.. important:: + + Don't use the ``validates_associated`` helper on both ends of your + associations because this causes {+odm+} to perform validations in an + infinite loop. + +.. _mongoid-other-validation: + +Custom Validation Rules +~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``validates_each`` and ``validates_with`` helpers to +create custom validators. To learn more about these helpers and view +examples, see the `validates_each +`__ +and `validates_with +`__ +references in the Active Record documentation. + +To learn more about custom validators, see `Performing Custom +Validations +`__ +in the Active Record documentation. + +Behavior +-------- + +{+odm+} performs validation when you persist, or save, a document to the +database. The following methods trigger your validation rules, so +{+odm+} saves the object to the database only if it passes validation: + +- ``create()`` +- ``save()`` +- ``update()`` + +When you use the ``-!`` suffixed version of the preceding methods, +{+odm+} returns an ``Mongoid::Errors::Validations`` exception if +validation fails for an object. + +Trigger Validation +~~~~~~~~~~~~~~~~~~ + +You can run validations manually by using the ``valid?()`` method. This +method returns ``true`` if the object passes validation, and +``false`` otherwise: + +.. literalinclude:: /includes/data-modeling/validation.rb + :start-after: start-valid + :end-before: end-valid + :language: ruby + :emphasize-lines: 7, 11, 14 + :dedent: + +{+odm+} behaves differently from Active Record when running ``valid?()`` +on persisted data. Active Record's ``valid?()`` runs all +validations, but {+odm+}'s ``valid?()`` runs validations only on +documents that are in memory to optimize performance. + +Additional Information +---------------------- + +To learn more about validation methods and macros in {+odm+}, see the +:mongoid-api:`Mongoid::Validatable ` module +reference in the API documentation. + +To view a full list of validations helpers in Active Record, see the +`ActiveModel::Validations::HelperMethods +`__ +reference in the Rails API documentation. + +.. TODO link to field types guide. diff --git a/source/includes/data-modeling/validation.rb b/source/includes/data-modeling/validation.rb new file mode 100644 index 00000000..47b51e27 --- /dev/null +++ b/source/includes/data-modeling/validation.rb @@ -0,0 +1,89 @@ +# start-simple-val +class Person + include Mongoid::Document + + field :name, type: String + validates :name, presence: true +end +# end-simple-val + +# start-comparison +class Order + include Mongoid::Document + + field :order_date, type: DateTime + field :delivery_date, type: DateTime + field :quantity, type: Integer + + validates :delivery_date, comparison: { greater_than: :order_date } + validates :quantity, comparison: { less_than: 5 } +end +# end-comparison + +# start-fmt +class User + include Mongoid::Document + + field :username, type: String + + validates :username, format: { with: /\A[a-zA-Z]+\z/ } +end +# end-fmt + +# start-inclusion +class Order + include Mongoid::Document + + field :shipping, type: String + + validates :shipping, inclusion: { in: %w(standard priority overnight) } +end +# end-inclusion + +# start-absence +class Order + include Mongoid::Document + + field :delivery_date, type: String + + validates :delivery_date, absence: true +end +# end-absence + +# start-unique +class Person + include Mongoid::Document + + field :first_name, type: String + field :last_name, type: String + + validates :first_name, uniqueness: { scope: :last_name } +end +# end-unique + +# start-assoc +class Author + include Mongoid::Document + + embeds_many :books + + validates_associated :books +end +# end-assoc + +# start-valid +class Person + include Mongoid::Document + + field :name, type: String + field :age, type: Integer + + validates :age, comparison: { greater_than_or_equal_to: 0 } +end + +# Returns true +Person.new(name: "Berta Odom", age: 4).valid? + +# Returns false +Person.new(name: "Cody Peng", age: -5).valid? +# end-valid \ No newline at end of file diff --git a/source/interact-data.txt b/source/interact-data.txt index b11a2b05..2e3e3707 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -18,6 +18,7 @@ Interact with Data Modify Query Results Search Text Transactions and Sessions + Nested Attributes In this section, you can learn how to use {+odm+} to interact with your MongoDB data. @@ -32,4 +33,7 @@ MongoDB data. searches on text fields. - :ref:`mongoid-data-txn`: Learn how to perform multi-document - transactions to make atomic data changes. \ No newline at end of file + transactions to make atomic data changes. + +- :ref:`mongoid-data-nested-attr`: Learn how to modify documents and + their associations in a single operation. \ No newline at end of file diff --git a/source/data-modeling/nested-attributes.txt b/source/interact-data/nested-attributes.txt similarity index 99% rename from source/data-modeling/nested-attributes.txt rename to source/interact-data/nested-attributes.txt index 7e8296d2..fcd10dd9 100644 --- a/source/data-modeling/nested-attributes.txt +++ b/source/interact-data/nested-attributes.txt @@ -1,4 +1,4 @@ -.. _mongoid-modeling-nested-attr: +.. _mongoid-data-nested-attr: ================= Nested Attributes From 469d5623158d76cd588190da9c92ab4e1834337d Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:35:19 -0800 Subject: [PATCH 090/113] DOCSP-44794 Field Types (#69) --- source/data-modeling.txt | 6 +- source/data-modeling/field-types.txt | 581 +++++++++++++++++++ source/includes/data-modeling/field-types.rb | 247 ++++++++ 3 files changed, 833 insertions(+), 1 deletion(-) create mode 100644 source/data-modeling/field-types.txt create mode 100644 source/includes/data-modeling/field-types.rb diff --git a/source/data-modeling.txt b/source/data-modeling.txt index 50af17cf..af6b80ca 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -13,8 +13,9 @@ Model Your Data .. toctree:: :caption: Data Modeling - + Documents + Field Types Field Behaviors Inheritance Document Validation @@ -24,6 +25,9 @@ In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-modeling-documents`: Learn about the ``Document`` module. +- :ref:`mongoid-field-types`: Learn about the field types that you can use in + {+odm+} to define the schema for your MongoDB documents. + - :ref:`mongoid-field-behaviors`: Learn how to customize the behaviors of fields in {+odm+} to meet your application requirements. diff --git a/source/data-modeling/field-types.txt b/source/data-modeling/field-types.txt new file mode 100644 index 00000000..1d4df189 --- /dev/null +++ b/source/data-modeling/field-types.txt @@ -0,0 +1,581 @@ +.. _mongoid-field-types: + +=========== +Field Types +=========== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: fields, data types, type conversion, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn about the field types supported in +{+odm+} that you can use to define the schema for your MongoDB documents. + +MongoDB uses :manual:`BSON types ` to represent the data +types stored in document fields. To use BSON data in a {+odm+} application, +{+odm+} must convert the BSON types to {+language+} types at runtime. For example, +when retrieving a document from the database, {+odm+} translates a BSON +``double`` type to use the {+language+} ``Float`` type. When you save the +document again, {+odm+} converts the field back to a BSON ``double``. + +.. TODO: To learn more about modeling documents in {+odm+}, see :ref:`mongoid-modeling-documents`. + +.. note:: + + Modifying the field definition in a model class does not change any data + stored in the database. To change the data type of a field in the + database, you must re-save the data again. + +Field Types +----------- + +You can define field names and types in model classes by using the ``field`` +and ``type`` macros. The following example defines the fields of a ``Person`` +class: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-define-fields + :end-before: # end-define-fields + +The following list provides the field types that you can use in {+odm+}: + +- ``Array`` +- ``Bson::Binary`` +- ``BigDecimal`` +- ``Mongoid::Boolean`` or ``Boolean`` +- ``Date`` +- ``DateTime`` +- ``Float`` +- ``Hash`` +- ``Integer`` +- ``Object`` +- ``Bson::ObjectId`` +- ``Range`` +- ``Regexp`` +- ``Set`` +- ``String`` +- ``Mongoid::StringifiedSymbol`` +- ``Time`` +- ``ActiveSupport::TimeWithZone`` + +.. note:: + + {+odm+} does not support ``BSON::Int64`` or ``BSON::Int32`` as field types. + {+odm+} saves these values to the database correctly, but when you retrieve + the documents, the fields are returned as ``Integer`` types. + + Similarly, when querying fields with the ``BSON::Decimal128`` type, {+odm+} + returns the fields as ``BigDecimal`` types. + +Untyped Fields +~~~~~~~~~~~~~~ + +If you don't specify a type for a field, {+odm+} interprets it as the default ``Object`` +type. An untyped field can store values of any type that is directly +serializable to BSON. You can leave a field untyped if the field might contain +different types of data, or if the type of the field's value is not known. + +The following example defines a ``Product`` class with an untyped field: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-define-untyped + :end-before: # end-define-untyped + +The type of the ``properties`` field is ``Object`` but varies depending on +the type of data stored in that field. The following example saves data into the +``properties`` field in two different ways: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-untyped + :end-before: # end-untyped + +Because {+odm+} doesn't perform any type conversions on untyped fields when +reading from the database, values that require special handling might not be retrieved +correctly in as the value of an untyped field. Do not store the following BSON data types +in untyped fields: + +- ``Date``: Returns as ``Time`` in untyped fields +- ``DateTime``: Returns as ``Time`` in untyped fields +- ``Range``: Returns as ``Hash`` in untyped fields + +Hash +~~~~ + +You can store ``Hash`` data in a field by using the ``Hash`` type. When you specify +a field as a ``Hash``, ensure that you follow the MongoDB :manual:`Naming +Restrictions ` to ensure that the values +store properly in the database. + +The following example creates a ``Person`` class and specifies the ``url`` field +as a ``Hash``. + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-hash + :end-before: # end-hash + +Time +~~~~ + +You can store values as BSON ``Time`` instances by using the ``Time`` field value. +``Time`` fields are stored in the time zone configured for your application. To +learn more about configuring time zones, see the :ref:`Time Zones ` +guide. + +.. TODO: Update Time Zones guide ref if it's changed during standardization + +The following example creates a ``Voter`` class and specifies that the value of the +``registered_at`` field is a ``Time`` type: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-time + :end-before: # end-time + +.. note:: + + Storing a ``Date`` or ``DateTime`` value in a field specified as ``Time`` + converts the value to ``Time`` when assigned. If you store a string in a + ``Time`` field, {+odm+} parses the string by using the ``Time.parse()`` + method. + +.. TODO: Add this to the note: +.. To learn more about how {+odm+} converts queries, see :ref:`` + +Date +~~~~ + +You can store the following value types in a field specified as a ``Date``: + +- ``Date``: Stores the value as provided. +- ``Time``: Stores the date portion of the value in the value's time zone. +- ``DateTime``: Stores the date portion of the value in the value's time zone. +- ``ActiveSupport::TimeWithZone``: Stores the date portion of the value in the + value's time zone. +- ``String``: Stores the date specified in the string. +- ``Integer``: Takes the value as if it is a UTC timestamp and + converts it into your application's configured time zone. {+odm+} then stores + the date taken from that timestamp. +- ``Float``: Takes the value as if it is a UTC timestamp and + converts it into your application's configured time zone. {+odm+} then stores + the date taken from that timestamp. + +Because converting a ``Time`` or ``DateTime`` discards the time portion, we +recommend explicitly converting ``String``, ``Time``, and ``DateTime``, objects +to ``Date`` before assigning them to the field. + +.. note:: + + When a database contains a string value for a ``Date`` field, the driver + parses the value by using the ``Time.parse()`` method, then discards the time + portion. ``Time.parse()`` considers values without time zones to be in local + time. + +.. TODO: Add this to the note: +.. To learn more about how {+odm+} converts queries, see :ref:`` + +DateTime +~~~~~~~~ + +When you assign a value to a field defined as a ``DateTime`` or query on these fields, {+odm+} converts +the value to a UTC ``Time`` value before sending it to the MongoDB server. +{+odm+} saves the value with the time zone embedded in the ``DateTime`` object. When +you retrieve the value, {+odm+} converts the UTC time to the time zone +configured for your application. + +The following example creates a ``Ticket`` class and specifies the ``purchased_at`` +field as a ``DateTime`` field: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-datetime + :end-before: # end-datetime + +If you save an integer or float value to a ``DateTime`` field, the value is treated as +a Unix timestamp in UTC. The following example saves an integer value to the +``purchased_at`` field: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-datetime-int + :end-before: # end-datetime-int + +If you save a string value to a ``DateTime`` field, {+odm+} saves the ticket +with the time zone specified. If a time zone is not specified, {+odm+} saves the +value by using the timezone configured as the default for your application: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-datetime-string + :end-before: # end-datetime-string + +To learn more about configuring time zones, see the :ref:`Time Zones ` +guide. + +.. TODO: Update Time Zones guide ref if it's changed during standardization + +.. note:: + + {+odm+} parses string values into ``DateTime`` by using the ``Time.parse()`` + method, which considers values without time zones to be in local time. + +Timestamps +~~~~~~~~~~ + +You can include timestamp fields in a class by including the ``Mongoid::Timestamps`` +module when you create your class. When you include the ``Mongoid::Timestamps``, +{+odm+} creates the following fields in your class: + +- ``created_at``: Stores the time the document was created. +- ``updated_at``: Stores the time the document was last updated. + +The following example creates a ``Post`` class with timestamp fields: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-timestamps + :end-before: # end-timestamps + +You can also choose to include only the ``created_at`` or ``updated_at`` fields +by including only the ``Created`` or ``Updated`` modules. The following example +creates a ``Post`` class with only the ``created_at`` field, and a ``Post`` +class with only the ``updated_at`` field: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-timestamps-specific + :end-before: # end-timestamps-specific + +You can shorten the timestamp field names to ``c_at`` and ``u_at`` by setting +the ``::Short`` option when including the module: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-timestamps-short + :end-before: # end-timestamps-short + +You can disable creating the timestamp field for specific operations by calling +the ``timeless()`` method on the method call. The following example disables the +timestamps for the ``save`` operation: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-timestamps-disable + :end-before: # end-timestamps-disable + +Regexp +~~~~~~ + +You can store regular expressions in a field by using the ``Regexp`` type. + +While MongoDB implements `Perl Compatible Regular Expressions (PCRE) `__, +{+odm+} uses {+language+}'s `Onigmo `__ library. PCRE and +Onigmo provide generally similar functionality, but there are several syntax +differences. For example, Onigmo uses ``\A`` and ``\z`` to match the beginning and +end of a string, while PCRE uses ``^`` and ``$``. + +When you declare a field as a ``Regexp``, {+odm+} converts {+language+} regular +expressions to BSON regular expressions when storing the result into your +database. The database returns the field as a ``Bson::Regexp::Raw`` instance. +You can use the ``compile()`` method on ``BSON::Regexp::Raw`` instances to convert +the data back to a {+language+} regular expression. + +The following example creates a ``Token`` class and specifies the ``pattern`` +field as a ``Regexp``: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-regexp + :end-before: # end-regexp + +.. important:: + + Converting a BSON regular expression to a {+language+} regular expression might + produce a different regular expression than the original. This difference is + due to the differences between the Onigmo and PCRE syntaxes. + +.. TODO: To learn more about regular expressions in {+odm+}, see :ref:`mongoid-regular-expressions`. + +BigDecimal +~~~~~~~~~~ + +You can use the ``BigDecimal`` type to store numbers with increased precision. +{+odm+} stores ``BigDecimal`` values in two different ways, depending on the +value you set for the ``Mongoid.map_big_decimal_to_decimal128`` configuration property: + +- If set to ``true``, {+odm+} stores ``BigDecimal`` values as BSON ``Decimal128`` + values. +- If set to ``false`` (default), {+odm+} stores ``BigDecimal`` values as strings. + +Consider the following limitations when setting the +``Mongoid.map_big_decimal_to_decimal128`` option to ``true``: + +- ``Decimal128`` has a limited range and precision. + ``Decimal128`` has a maximum value of approximately ``10^6145`` and a minimum + of approximately ``-10^6145``, with a maximum of 34 bits of precision. If you + are storing values that are outside of these limits, we recommend storing them + as strings instead. +- ``Decimal128`` accepts signed ``NaN`` values, but ``BigDecimal`` does not. + Retrieving signed ``NaN`` ``Decimal128`` values from the database as + ``BigDecimal`` returns the value unsigned. +- ``Decimal128`` maintains trailing zeroes, but ``BigDecimal`` does not. + Because of this, retrieving ``Decimal128`` values from the database as + ``BigDecimal`` might result in a loss of precision. + +.. note:: + + When you set the ``Mongoid.map_big_decimal_to_decimal128`` option to ``false`` + and store a ``BigDecimal`` into an untyped field, you cannot query the field + as a ``BigDecimal``. Because the value is stored as a string, querying + the untyped field for a ``BigDecimal`` value does not find the value in the + database. To find the value, you must first convert the query value to a string. + + You can avoid this issue by specifying the field as a ``BigDecimal`` type, + instead of as untyped. + +StringifiedSymbol +~~~~~~~~~~~~~~~~~ + +Use the ``StringifiedSymbol`` field type to store values that should be exposed +as symbols to {+language+} applications. ``StringifiedSymbol`` allows you to use symbols +while ensuring interoperability with other drivers. This type stores all data on +the database as strings, and converts the strings to symbols when read by the +application. Values that cannot be directly converted to symbols, such as +integers and arrays, are converted into strings and then into symbols. + +The following example defines the ``status`` field as a ``StringifiedSymbol`` and +demonstrates how the field is stored and returned: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-stringified-symbol + :end-before: # end-stringified-symbol + +Specify Field Types as Strings or Symbols +----------------------------------------- + +You can use strings or symbols to specify certain field types in {+odm+}, instead of +using their class names. The following example specifies the ``order_num`` field by +using the class name, a string, and a symbol: + +.. code-block:: ruby + + class Order + include Mongoid::Document + + # Class Name + field :order_num, type: Integer + + # Symbol + field :order_num, type: :integer + + # String + field :order_num, type: "integer" + end + +The following table provides the field types that can you can specify as strings or symbols: + +.. list-table:: + :header-rows: 1 + + * - Class Name + - Symbol + - String + + * - ``Array`` + - ``:array`` + - ``"Array"`` + + * - ``BigDecimal`` + - ``:big_decimal`` + - ``"BigDecimal"`` + + * - ``BSON::Binary`` + - ``:binary`` + - ``"BSON::Binary"`` + + * - ``Mongoid::Boolean`` + - ``:boolean`` + - ``"Mongoid::Boolean"`` + + * - ``Date`` + - ``:date`` + - ``"Date"`` + + * - ``DateTime`` + - ``:date_time`` + - ``"DateTime"`` + + * - ``Float`` + - ``:float`` + - ``"Float"`` + + * - ``Hash`` + - ``:hash`` + - ``"Hash"`` + + * - ``Integer`` + - ``:integer`` + - ``"Integer"`` + + * - ``BSON::ObjectId`` + - ``:object_id`` + - ``"BSON::ObjectId"`` + + * - ``Range`` + - ``:range`` + - ``"Range"`` + + * - ``Regexp`` + - ``:regexp`` + - ``"Regexp"`` + + * - ``Set`` + - ``:set`` + - ``"Set"`` + + * - ``String`` + - ``:string`` + - ``"String"`` + + * - ``StringifiedSymbol`` + - ``:stringified_symbol`` + - ``"StringifiedSymbol"`` + + * - ``Symbol`` + - ``:symbol`` + - ``"Symbol"`` + + * - ``Time`` + - ``:time`` + - ``"Time"`` + +Custom Field Types +------------------ + +You can create custom field types and define how {+odm+} serializes and +deserializes them. To create a custom field type, define a class that +implements the following methods: + +- ``mongoize()``: Takes an instance of your custom type and converts it to + an object that MongoDB can store. +- ``demongoize()``: Takes an object from MongoDB and converts it to an + instance of your custom type. +- ``evolve()``: Takes an instance of your custom type and converts it to a + criteria that MongoDB can use to query the database. + +The following example creates a custom field type called ``Point`` and +implements the preceding methods: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-custom-field-type + :end-before: # end-custom-field-type + +In the preceding example, the ``mongoize()`` *instance method* accepts an instance +of your custom type object and converts it to an ``Array`` to store in the +database. The ``mongoize()`` *class method* accepts objects of all types and +converts them to similar types that can be stored in the database. {+odm+} uses +the ``mongoize()`` class method when it calls the getter and setter methods. + +The ``demongoize()`` method converts the stored ``Array`` value into the custom +``Point`` type. The {+odm+} uses this method when it calls the getter. + +The ``evolve()`` method converts the custom ``Point`` type into a queryable +``Array`` type, and converts all other types to ``object``. {+odm+} uses this +method when it calls a method that queries the database. + +Phantom Custom Field Types +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can create a custom field type that saves a different value to the database +than the value assigned in the application. This can be useful to have +descriptive values in the application while storing more compact values in the +database. + +The following example creates a ``ColorMapping`` type that uses the name of the +color in the application, but stores the color as an integer in the database: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-phantom-field-type + :end-before: # end-phantom-field-type + +Dynamic Fields +-------------- + +You can instruct {+odm+} to create fields dynamically by inluding the +``Mongoid::Attributes::Dynamic`` module in your model. This allows {+odm+} to +create fields based on an arbitrary hash, or based on the documents already +stored in the database. + +The following example creates a ``Person`` class with dynamic fields: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-dynamic-field + :end-before: # end-dynamic-field + +.. tip:: + + You can specify both fixed fields and dynamic fields within the same class. + In this case, {+odm+} treats all attributes for properties with field definitions + according to their field type, and all other attributes as dynamic. + +When using dynamic fields in your application, you must initially set the value in one of the +following ways: + +- Pass the attribute hash to the constructor. +- Assign values by using the ``attributes=`` method. +- Assign values by using the ``[]=`` method. +- Assign values by using the ``write_attribute`` method. +- Work with values that are already present in the database. + +If you don't initially set the value by using one of the preceding options, +invoking the attribute returns a ``NoMethodError``. + +Reserved Characters +------------------- + +Both {+odm+} and the MongoDB Query API reserve the ``.`` character to separate field +names in nested documents and the ``$`` character at the beginning of a +string to indicate a query operator. Because of this, you should avoid using these +characters in your field names. + +If your application requires the use of these characters, you can access the +fields by calling the ``send()`` method. The following example creates a ``User`` +class with fields that contain reserved characters. It then accesses the fields +by using the ``send()`` method: + +.. literalinclude:: /includes/data-modeling/field-types.rb + :language: ruby + :start-after: # start-reserved-characters + :end-before: # end-reserved-characters + +You can also access these fields by calling the ``read_attribute()`` method. + +.. important:: + + Because updating and replacing fields containing these reserved characters + requires special operators, calling getters and setters on these fields + raises an ``InvalidDotDollarAssignment`` exception. + +.. Additional Information +.. ---------------------- + +.. TODO: Add Additional Information section with links to relevant pages once they are standardized diff --git a/source/includes/data-modeling/field-types.rb b/source/includes/data-modeling/field-types.rb new file mode 100644 index 00000000..f740815a --- /dev/null +++ b/source/includes/data-modeling/field-types.rb @@ -0,0 +1,247 @@ +# start-define-fields +class Person + include Mongoid::Document + field :name, type: String + field :date_of_birth, type: Date + field :weight, type: Float +end +# end-define-fields + +# start-define-untyped +class Product + include Mongoid::Document + + field :name, type: String + field :properties +end +# end-define-untyped + +# start-untyped +product = Product.new(properties: "color=white,size=large") +# properties field saved as String: "color=white,size=large" + +product = Product.new(properties: {color: "white", size: "large"}) +# properties field saved as Object: {:color=>"white", :size=>"large"} +# end-untyped + +# start-stringified-symbol +class Post + include Mongoid::Document + + field :status, type: StringifiedSymbol +end + +# Save status as a symbol +post = Post.new(status: :hello) +# status is stored as "hello" on the database, but returned as a Symbol +post.status +# Outputs: :hello + +# Save status as a string +post = Post.new(status: "hello") +# status is stored as "hello" in the database, but returned as a Symbol +post.status +# Outputs: :hello +# end-stringified-symbol + +# start-hash +class Person + include Mongoid::Document + field :first_name + field :url, type: Hash +end + +person = Person.new(url: {'home_page' => 'http://www.homepage.com'}) +# end-hash + +# start-time +class Voter + include Mongoid::Document + + field :registered_at, type: Time +end + +Voter.new(registered_at: Date.today) +# end-time + +# start-datetime +class Ticket + include Mongoid::Document + field :purchased_at, type: DateTime +end +# end-datetime + +# start-datetime-int +ticket.purchased_at = 1544803974 +ticket.purchased_at +# Outputs: Fri, 14 Dec 2018 16:12:54 +0000 +# end-datetime-int + +# start-datetime-string +ticket.purchased_at = 'Mar 4, 2018 10:00:00 +01:00' +ticket.purchased_at +# Outputs: Sun, 04 Mar 2018 09:00:00 +0000 +# end-datetime-string + +# start-timestamps +class Post + include Mongoid::Document + include Mongoid::Timestamps +end +# end-timestamps + +# start-timestamps-specific +class Post + include Mongoid::Document + include Mongoid::Timestamps::Created +end + +class Post + include Mongoid::Document + include Mongoid::Timestamps::Updated +end +# end-timestamps-specific + +# start-timestamps-disable +post.timeless.save +# end-timestamps-disable + +# start-timestamps-short +class Post + include Mongoid::Document + include Mongoid::Timestamps::Short # For c_at and u_at. +end + +class Post + include Mongoid::Document + include Mongoid::Timestamps::Created::Short # For c_at only. +end + +class Post + include Mongoid::Document + include Mongoid::Timestamps::Updated::Short # For u_at only. +end +# end-timestamps-short + +# start-regexp +class Token + include Mongoid::Document + + field :pattern, type: Regexp +end + +token = Token.create!(pattern: /hello.world/m) +token.pattern +# Outputs: /hello.world/m + +# Reload the token from the database +token.reload +token.pattern +# Outputs: # +# end-regexp + +# start-custom-field-type +class Point + + attr_reader :x, :y + + def initialize(x, y) + @x, @y = x, y + end + + # Converts an object of this instance into an array + def mongoize + [ x, y ] + end + + class << self + + # Takes any possible object and converts it to how it is + # stored in the database. + def mongoize(object) + case object + when Point then object.mongoize + when Hash then Point.new(object[:x], object[:y]).mongoize + else object + end + end + + # Gets the object as it's stored in the database and instantiates + # this custom class from it. + def demongoize(object) + if object.is_a?(Array) && object.length == 2 + Point.new(object[0], object[1]) + end + end + + # Converts the object supplied to a criteria and converts it + # into a queryable form. + def evolve(object) + case object + when Point then object.mongoize + else object + end + end + end +end +# end-custom-field-type + +# start-phantom-field-type +class ColorMapping + + MAPPING = { + 'black' => 0, + 'white' => 1, + }.freeze + + INVERSE_MAPPING = MAPPING.invert.freeze + + class << self + + def mongoize(object) + MAPPING[object] + end + + def demongoize(object) + INVERSE_MAPPING[object] + end + + def evolve(object) + MAPPING.fetch(object, object) + end + end +end + +class Profile + include Mongoid::Document + field :color, type: ColorMapping +end + +profile = Profile.new(color: 'white') +profile.color +# Outputs: "white" + +# Sets "color" field to 0 in MongoDB +profile.save! +# end-phantom-field-type + +# start-dynamic-field +class Person + include Mongoid::Document + include Mongoid::Attributes::Dynamic +end +# end-dynamic-field + +# start-reserved-characters +class User + include Mongoid::Document + field :"first.last", type: String + field :"$_amount", type: Integer +end + +user = User.first +user.send(:"first.last") +# Outputs: Mike.Trout +user.send(:"$_amount") +# Outputs: 42650000 +# end-reserved-characters \ No newline at end of file From ca9a84d5bf3ca1ef4193618be49f4bcc0fbd556b Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Mon, 9 Dec 2024 07:03:30 -0800 Subject: [PATCH 091/113] DOCSP-45357 Sharding Configuration (#76) --- snooty.toml | 5 +- source/configuration.txt | 22 +++ source/configuration/sharding.txt | 169 +++++++++++++++++++++ source/data-relationships/associations.txt | 23 +++ source/includes/configuration/sharding.rb | 58 +++++++ source/index.txt | 7 +- source/reference/indexes.txt | 1 + 7 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 source/configuration.txt create mode 100644 source/configuration/sharding.txt create mode 100644 source/data-relationships/associations.txt create mode 100644 source/includes/configuration/sharding.rb diff --git a/snooty.toml b/snooty.toml index 789f516a..d3adf1c5 100644 --- a/snooty.toml +++ b/snooty.toml @@ -11,8 +11,9 @@ toc_landing_pages = [ "/quick-start-sinatra", "/interact-data", "/interact-data/specify-query", - "/data-modeling" - ] + "/data-modeling", + "/configuration", +] [constants] rails-6-version = 6.0 diff --git a/source/configuration.txt b/source/configuration.txt new file mode 100644 index 00000000..432107ac --- /dev/null +++ b/source/configuration.txt @@ -0,0 +1,22 @@ +.. _mongoid-configuration: + +============= +Configuration +============= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, setup, config + +.. toctree:: + :caption: Configuration + + Sharding + +In this section, you can learn how to configure different options with {+odm+}. + +- :ref:`Sharding Configuration `: Learn how to configure + sharding in your {+odm+} application. diff --git a/source/configuration/sharding.txt b/source/configuration/sharding.txt new file mode 100644 index 00000000..52d77a28 --- /dev/null +++ b/source/configuration/sharding.txt @@ -0,0 +1,169 @@ +.. _mongoid-sharding-configuration: + +====================== +Sharding Configuration +====================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +Sharding is a way to distribute your data across multiple machines. MongoDB +uses sharding to support deployments with large data sets and high +throughput operations. In this guide, you can learn how to configure sharding in +your {+odm+} application. + +Declare Shard keys +------------------ + +MongoDB uses shard keys to distribute a collection's documents across +shards. A shard key is an indexed field, or multiple fields covered by a +compound index, that determines the distribution of the collection's +documents among the cluster's shards. In your +{+odm+} application, you can declare a shard key by +using the ``shard_key`` macro when you create a model. + +The following example creates a ``Person`` class with a shard key on the +``ssn`` field: + +.. literalinclude:: /includes/configuration/sharding.rb + :language: ruby + :start-after: # start-shard-key + :end-before: # end-shard-key + :emphasize-lines: 6 + +.. note:: + + To shard a collection, the collection must have an index that starts with the + shard key. The index can be on only the shard key, or it can be a compound index + where the shard key is the prefix. You can use {+odm+}'s index-management + functionality to create the index. To learn more about index management with + {+odm+}, see the :ref:`Index Management ` guide. + +If a model declares a shard key, {+odm+} expects the sharded collection to +use the declared key for sharding. When {+odm+} reloads models, it provides +the shard key along with the ``_id`` field to the ``find`` command to improve query +performance. If the collection is not sharded with the specified shard key, +queries might not return the expected results. + +Syntax +~~~~~~ + +You can declare shard keys by using either the full MongoDB +syntax or by using a shorthand syntax. + +The full syntax follows the format +of the ``mongosh`` :manual:`shardCollection() +` method, and allows you to specify the +following types of shard keys: + +- Ranged keys +- Hashed keys +- Compound keys + +The full syntax also allows you to specify collection and sharding options. + +The following example creates each of the preceding type of shard key on the +``sson`` field: + +.. literalinclude:: /includes/configuration/sharding.rb + :language: ruby + :start-after: # start-shard-key-formats + :end-before: # end-shard-key-formats + +The shorthand syntax allows you to declare a shard key by specifying only the +field name. This syntax supports only ranged and compound shard keys, and does not allow you +to specify collection or sharding options. + +The following example creates a ranged and a compound shard key: + +.. literalinclude:: /includes/configuration/sharding.rb + :language: ruby + :start-after: # start-shard-key-shorthand + :end-before: # end-shard-key-shorthand + +Specify Associated and Embedded Fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can specify a shard key on a ``belongs_to`` association in place of a field +name. When doing so, {+odm+} creates the shard key on the primary key of the +associated collection. + +The following example creates a shard key on the ``belongs_to`` association in a +``Person`` model. Because the associated ``country`` collection has a primary +key called ``country_id``, {+odm+} shards by that field: + +.. literalinclude:: /includes/configuration/sharding.rb + :language: ruby + :start-after: # start-shard-key-association + :end-before: # end-shard-key-association + +You can specify a shard key on an embedded document by using dot notation to +delimit the field names. The following example creates a shard key on the +``address.city`` field: + +.. literalinclude:: /includes/configuration/sharding.rb + :language: ruby + :start-after: # start-shard-key-embedded + :end-before: # end-shard-key-embedded + +.. note:: + + Because the period (``.``) character is used to delimit embedded fields, {+odm+} does + not support creating shard keys on fields with names that contain a period + character. + +Sharding Management Rake Tasks +------------------------------ + +You can shard collections in your database according to the shard keys defined +in your {+odm+} models by running the ``db:mongoid:shard_collections`` rake +task. To ensure that the collections contain indexes that start with the shard +key, you can first run the ``db:mongoid:create_indexes`` rake task. + +Run the following rake commands to create the indexes and shard the collections +based on your model's shard keys: + +.. code-block:: bash + + rake db:mongoid:create_indexes + rake db:mongoid:shard_collections + +Index management and sharding rake tasks do not stop when they encounter an +error with a particular model class. Instead, they log the error and continue +processing the next model. To ensure the rake tasks did not encounter any +errors, check the output of the {+odm+} logger configured for your application. + +.. note:: + + When performing schema-related operations in a sharded cluster, nodes might + contain out-of-date local configuration-related cache data. To clear the caches, + run the :manual:`flushRouterConfig ` + command on each ``mongos`` node. + + +Additional Information +---------------------- + +To learn more about sharding with MongoDB, see the :manual:`Sharding +` guide in the MongoDB {+server-manual+}. + +API Documentation +~~~~~~~~~~~~~~~~~ + +To learn more about the ``shard_key`` macro discussed in this +guide, see the `shard_key +<{+api-root+}/Shardable/ClassMethods.html#shard_key-instance_method>`__ API +documentation. diff --git a/source/data-relationships/associations.txt b/source/data-relationships/associations.txt new file mode 100644 index 00000000..a8224430 --- /dev/null +++ b/source/data-relationships/associations.txt @@ -0,0 +1,23 @@ +.. _mongoid_associations: + +============ +Associations +============ + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +Associations in {+odm+} allow you to create relationships between models. \ No newline at end of file diff --git a/source/includes/configuration/sharding.rb b/source/includes/configuration/sharding.rb new file mode 100644 index 00000000..878fb5f3 --- /dev/null +++ b/source/includes/configuration/sharding.rb @@ -0,0 +1,58 @@ +# start-shard-key +class Person + include Mongoid::Document + + field :ssn + + shard_key ssn: 1 + + # The collection must also have an index that starts with the shard key. + index ssn: 1 +end +# end-shard-key + +# start-shard-key-formats +# Create a ranged shard key +shard_key ssn: 1 + +# Create a compound shard key +shard_key ssn: 1, country: 1 + +# Create a hashed shard key +shard_key ssn: :hashed + +# Specify a shard key option +shard_key {ssn: :hashed}, unique: true +# end-shard-key-formats + +# start-shard-key-shorthand +# Create a ranged shard key +shard_key :ssn + +# Create a compound shard key +shard_key :ssn, :country +# end-shard-key-shorthand + +# start-shard-key-association +class Person + include Mongoid::Document + + belongs_to :country + + # Shards by country_id + shard_key country: 1 + + # The collection must have an index that starts with the shard key + index country: 1 +end +# end-shard-key-association + +# start-shard-key-embedded +class Person + include Mongoid::Document + + field :address + + shard_key "address.city" +end +# end-shard-key-embedded \ No newline at end of file diff --git a/source/index.txt b/source/index.txt index 53f3a002..75728bb0 100644 --- a/source/index.txt +++ b/source/index.txt @@ -17,9 +17,10 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Add {+odm+} to an Existing Application Interact with Data Model Your Data - installation-configuration - tutorials - schema-configuration + Configuration + .. installation-configuration + .. tutorials + .. schema-configuration working-with-data API /release-notes diff --git a/source/reference/indexes.txt b/source/reference/indexes.txt index 7a5d11a4..d6bc0eeb 100644 --- a/source/reference/indexes.txt +++ b/source/reference/indexes.txt @@ -1,3 +1,4 @@ +.. _mongoid-indexes: .. _indexes: **************** From 282344bca428f25a83b9a7e6952ff43515861e7e Mon Sep 17 00:00:00 2001 From: Maya Raman Date: Tue, 10 Dec 2024 10:43:49 -0500 Subject: [PATCH 092/113] DOCSP-42762: Indexes (#74) --- source/data-modeling.txt | 4 + source/data-modeling/indexes.txt | 219 +++++++++++++++++++++++ source/includes/data-modeling/indexes.rb | 123 +++++++++++++ source/reference/indexes.txt | 2 +- 4 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 source/data-modeling/indexes.txt create mode 100644 source/includes/data-modeling/indexes.rb diff --git a/source/data-modeling.txt b/source/data-modeling.txt index af6b80ca..a0ed3526 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -19,6 +19,7 @@ Model Your Data Field Behaviors Inheritance Document Validation + Optimize Queries With Indexes In this section, you can learn how to model data in {+odm+}. @@ -36,3 +37,6 @@ In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-modeling-validation`: Learn how to create document validation rules for your model classes. + +- :ref:`mongoid-optimize-queries-with-indexes`: Learn how to create and manage + indexes for your model classes. diff --git a/source/data-modeling/indexes.txt b/source/data-modeling/indexes.txt new file mode 100644 index 00000000..6e8dcbd6 --- /dev/null +++ b/source/data-modeling/indexes.txt @@ -0,0 +1,219 @@ +.. _mongoid-optimize-queries-with-indexes: + +============================= +Optimize Queries With Indexes +============================= + +.. default-domain:: mongodb + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, odm, optimization, efficiency, Atlas search + +.. contents:: On this page + :local: + :backlinks: none + :depth: 3 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use **indexes** with {+odm+}. Indexes can +improve the efficiency of queries by limiting the number of documents MongoDB +needs to scan. If your application is repeatedly running queries +on certain fields, you can create an index on those fields to improve query +performance. + +The following sections in this guide describe how to declare and create different +types of indexes using {+odm+}. The examples use the ``Restaurant`` model, which +maps to the ``restaurants`` collection in the ``sample_restaurants`` database. +To learn how to connect to this database +and collection using {+odm+}, see the :ref:`mongoid-quick-start-rails` or +:ref:`mongoid-quick-start-sinatra` guides. + +Declare and Create an Index +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using {+odm+}, you can declare your index using the ``index`` macro and +then create it using the ``create_indexes`` command. + +The following code example shows how to declare and create an ascending index +named ``cuisine_index`` on the ``cuisine`` field in the ``Restaurant`` class: + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :emphasize-lines: 8, 11 + :start-after: start create index + :end-before: end create index + +The ``index`` macro defines the index you want to create and the ``create_indexes`` +command creates it in the ``restaurants`` collection. + +When defining an index, the first hash object contains the field you want to +index and its direction. ``1`` represents an ascending index, and ``-1`` represents a +descending index. The second hash object contains index options. To learn more +about index options, see the :ref:`mongoid-indexes-api-documentation` section. + +Aliases and Declaring Indexes ++++++++++++++++++++++++++++++ + +You can use aliased field names in index definitions. For example, the following +code creates an index on the ``b`` field, which is an alias of the ``borough`` +field: + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start create alias index + :end-before: end create alias index + +Create an Index on Embedded Document Fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can define an index on embedded document fields. The following code example +shows how to declare an ascending index on the ``street`` field, which is embedded +within the ``address`` field in the ``Restaurant`` model. + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start create embedded index + :end-before: end create embedded index + +Create a Compound Index +~~~~~~~~~~~~~~~~~~~~~~~ + +You can define a compound index on multiple fields. The following code example +shows how to declare a compound index that is ascending on the ``borough`` +field and descending on the ``name`` field. + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start create compound index + :end-before: end create compound index + +Create a Geospatial Index +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can define a 2dsphere index on fields that contain GeoJSON objects or +coordinate pairs. +The following example defines a 2dsphere index on a field that contains GeoJSON +objects: + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start create 2dsphere index + :end-before: end create 2dsphere index + +For more information on 2dsphere indexes, see the :manual:`2dsphere ` +guide in the MongoDB {+server-manual+}. + +For more information on the GeoJSON type, see the :manual:`GeoJSON Objects ` +guide in the MongoDB {+server-manual+}. + +Create a Sparse Index +~~~~~~~~~~~~~~~~~~~~~ + +You can define a sparse index on fields that are not present in all documents. +The following code example defines a sparse index on the ``borough`` field: + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start create sparse index + :end-before: end create sparse index + +For more information on sparse indexes, see the :manual:`Sparse Indexes ` +guide in the MongoDB {+server-manual+}. + +Create Multiple Indexes +~~~~~~~~~~~~~~~~~~~~~~~ + +You can define multiple indexes within your model and create them using a single +``create_indexes`` call. The following example shows how to create multiple +indexes at the same time: + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start create multiple indexes + :end-before: end create multiple indexes + +Drop Indexes +~~~~~~~~~~~~ + +You can drop all indexes in your collection. The following example drops all +indexes in the ``Restaurant`` model: + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start drop indexes + :end-before: end drop indexes + +.. note:: Default Index + + MongoDB creates a default index on the ``_id`` field during the + creation of a collection. This index prevents clients from inserting + two documents with the same values for the ``_id`` field. You cannot + drop this index. + +Atlas Search Indexes +~~~~~~~~~~~~~~~~~~~~ + +You can declare and manage Atlas Search indexes using {+odm+}. + +To declare a search index, use the ``search_index`` macro within your model. To +create the search indexes declared within a model, use the ``create_search_indexes`` +command. The following code example shows how to declare and create an Atlas +Search index named ``my_search_index``. +The index is on the ``name`` and ``cuisine`` fields and is dynamic. + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start create atlas search index + :end-before: end create atlas search index + +To learn more about the syntax for creating an Atlas Search index, see +the :atlas:`Create an Atlas Search Index ` +guide in the MongoDB Atlas documentation. + +Remove an Atlas Search Index +++++++++++++++++++++++++++++ + +To remove an Atlas Search index, use the ``remove_search_indexes`` command. The +following code example shows how to remove an Atlas Search index from the +``restaurants`` collection: + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start remove atlas search index + :end-before: end remove atlas search index + +List Atlas Search Indexes ++++++++++++++++++++++++++ + +You can enumerate through all Atlas Search indexes in your collection +by using the ``search_indexes`` command. The following example enumerates through +all Atlas Search indexes in the ``restaurants`` collection and prints out their +information: + +.. literalinclude:: /includes/data-modeling/indexes.rb + :language: ruby + :start-after: start list atlas search index + :end-before: end list atlas search index + +.. _mongoid-indexes-api-documentation: + +API Documentation +----------------- + +To learn more about using indexes in {+odm+}, see the +`Mongoid::Indexable::ClassMethods <{+api-root+}/Indexable/ClassMethods.html>`__ +documentation. + +To learn more about index options, see the `Mongoid::Indexable::Validators::Options +<{+api-root+}/Indexable/Validators/Options.html>`__ documentation. + +To learn more about using Atlas Search indexes in {+odm+}, see the +`Mongoid::SearchIndexable::ClassMethods <{+api-root+}/SearchIndexable/ClassMethods.html>`__ +documentation. \ No newline at end of file diff --git a/source/includes/data-modeling/indexes.rb b/source/includes/data-modeling/indexes.rb new file mode 100644 index 00000000..9eebaab9 --- /dev/null +++ b/source/includes/data-modeling/indexes.rb @@ -0,0 +1,123 @@ +# start create index +class Restaurant + include Mongoid::Document + + field :name, type: String + field :cuisine, type: String + field :borough, type: String + + index({ cuisine: 1}, { name: "cuisine_index", unique: false }) +end + +Restaurant.create_indexes +# end create index + +# start create alias index +class Restaurant + include Mongoid::Document + + field :borough, as: :b + + index({ b: 1}, { name: "borough_index" }) +end +# end create alias index + +# start create embedded index +class Address + include Mongoid::Document + + field :street, type: String +end + +class Restaurant + include Mongoid::Document + + embeds_many :addresses + index({"addresses.street": 1}) +end +# end create embedded index + +# start create compound index +class Restaurant + include Mongoid::Document + + field :name, type: String + field :borough, type: String + + index({borough: 1, name: -1}, { name: "compound_index"}) +end +# end create compound index + +# start create 2dsphere index +class Restaurant + include Mongoid::Document + + field :location, type: Array + + index({location: "2dsphere"}, { name: "location_index"}) +end +# end create 2dsphere index + +# start create sparse index +class Restaurant + include Mongoid::Document + + field :name, type: String + field :cuisine, type: String + field :borough, type: String + + index({ borough: 1}, { sparse: true }) +end +# end create sparse index + +# start create multiple indexes +class Restaurant + include Mongoid::Document + + field :name, type: String + field :cuisine, type: String + field :borough, type: String + + index({ name: 1}) + index({ cuisine: -1}) +end + +Restaurant.create_indexes +# end create multiple indexes + +# start drop indexes +Restaurant.remove_indexes +# end drop indexes + +# start create atlas search index +class Restaurant + include Mongoid::Document + + field :name, type: String + field :cuisine, type: String + field :borough, type: String + + search_index :my_search_index, + mappings: { + fields: { + name: { + type: "string" + }, + cuisine: { + type: "string" + } + }, + dynamic: true + } +end + +Restaurant.create_search_indexes +# end create atlas search index + +# start remove atlas search index +Restaurant.remove_search_indexes +# end remove atlas search index + +#start list atlas search index +Restaurant.search_indexes.each { |index| puts index } +# end list atlas search index \ No newline at end of file diff --git a/source/reference/indexes.txt b/source/reference/indexes.txt index d6bc0eeb..f5de6805 100644 --- a/source/reference/indexes.txt +++ b/source/reference/indexes.txt @@ -276,4 +276,4 @@ explicitly requiring ``mongoid``: # Rakefile require 'bundler/setup' - load 'mongoid/tasks/database.rake' + load 'mongoid/tasks/database.rake' \ No newline at end of file From cffbe2600b041a91e0af247e646406e2bd9d0f2e Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Wed, 18 Dec 2024 08:52:48 -0800 Subject: [PATCH 093/113] DOCSP-45367 Associations pt. 1 (#79) --- source/data-modeling.txt | 4 + source/data-modeling/associations.txt | 517 ++++++++++++++++++ source/includes/data-modeling/associations.rb | 299 ++++++++++ source/reference/text-search.txt | 1 + source/reference/validation.txt | 1 + 5 files changed, 822 insertions(+) create mode 100644 source/data-modeling/associations.txt create mode 100644 source/includes/data-modeling/associations.rb diff --git a/source/data-modeling.txt b/source/data-modeling.txt index a0ed3526..e30a2248 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -19,6 +19,7 @@ Model Your Data Field Behaviors Inheritance Document Validation + Data Associations Optimize Queries With Indexes In this section, you can learn how to model data in {+odm+}. @@ -38,5 +39,8 @@ In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-modeling-validation`: Learn how to create document validation rules for your model classes. +- :ref:`mongoid-associations`: Learn how to create and manage data + associations in your model classes. + - :ref:`mongoid-optimize-queries-with-indexes`: Learn how to create and manage indexes for your model classes. diff --git a/source/data-modeling/associations.txt b/source/data-modeling/associations.txt new file mode 100644 index 00000000..f441f598 --- /dev/null +++ b/source/data-modeling/associations.txt @@ -0,0 +1,517 @@ +.. _mongoid-associations: + +============ +Associations +============ + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +Associations in {+odm+} allow you to create relationships between models. In +this guide, you can learn about the different types of associations that +{+odm+} supports and how to use them in your application. + +Referenced Associations +------------------------ + +Referenced associations allow you to create a relationship between two models +where one model references the other. {+odm+} supports the following referenced +association types: + +- ``has_one`` +- ``has_many`` +- ``belongs_to`` +- ``has_and_belongs_to_many`` + +The following sections describe how to use each of these association types. + +Has One +~~~~~~~ + +You can use the ``has_one`` macro to declare that documents represented by one class also contain a +document represented by a separate child class. The +following example creates a ``Band`` class with a ``has_one`` relationship to a +``Studio`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-one + :end-before: # end-has-one + +When you declare a ``has_one`` association, the child class must also use the +``belongs_to`` association that references the parent class. The following +example shows the ``Studio`` class referenced in the preceding ``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-one-child + :end-before: # end-has-one-child + +To learn more about the ``belongs_to`` macro, see the :ref:`Belongs To +` section. + +You can use validations to ensure that the child +class is present in your parent class, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-one-validation + :end-before: # end-has-one-validation + :emphasize-lines: 6 + +To learn more about validations in {+odm+}, see the :ref:`Validations +` guide. + +Has Many +~~~~~~~~ + +You can use the ``has_many`` macro to declare that documents represented by a class contain +multiple child documents represented by another class. The following +example creates a ``Band`` class with a ``has_many`` relationship to a +``Members`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-many + :end-before: # end-has-many + +When you declare a ``has_many`` association, the child class must also use the +``belongs_to`` association that references the parent class. The following +example shows the ``Member`` class referenced in the preceding ``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-many-child + :end-before: # end-has-many-child + +To learn more about the ``belongs_to`` macro, see the :ref:`Belongs To +` section. + +You can use validations to ensure that the child +class is present in your parent class, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-many-validation + :end-before: # end-has-many-validation + :emphasize-lines: 6 + +To learn more about validations in {+odm+}, see the :ref:`Validations +` guide. + +Retrieve Association Information +```````````````````````````````` + +You can use the ``any?`` method on a ``has_many`` association to determine if the +association contains any documents without retrieving the entire set of +documents from the database. + +The following example uses the ``any?`` method to determine if documents in the +``Band`` class contain any ``Members`` documents: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-many-any + :end-before: # end-has-many-any + +You can also use the ``any?`` method with a filter to find documents that match +a specified criteria, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-many-any-filter + :end-before: # end-has-many-any-filter + +You can supply a class name to the ``any?`` method to filter the results by the +name of the class. This is useful for polymorphic associations: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-many-any-class + :end-before: # end-has-many-any-class + +.. note:: + + After the data of the associated class is loaded to {+odm+}, subsequent calls + to the ``any?`` method do not query the database. Instead, {+odm+} uses the + data that is already loaded in memory. + +You can also call the ``exists?`` method to determine if there are any persisted +documents in the association. The ``exists?`` method always queries the +database and checks only for documents that have been saved to the database. +The ``exists?`` method does not allow for filtering and does not accept any +arguments. + +The following example uses the ``exists?`` method to determine if there are any persisted +``Members`` documents in the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-many-exists + :end-before: # end-has-many-exists + +.. _mongoid-belongs-to: + +Belongs To +~~~~~~~~~~ + +Use the ``belongs_to`` macro to declare that a document represented by one class +is a child of a document represented by another +class. By default, the ``_id`` field of the parent class is stored in the child +class. The following example creates a ``Members`` class with a ``belongs_to`` +association to a ``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-belongs-to + :end-before: # end-belongs-to + +You can allow {+odm+} to persist documents to the database without storing the +``_id`` of the associated parent class by setting the ``optional`` option to +``true``, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-belongs-to-optional + :end-before: # end-belongs-to-optional + +.. tip:: + + You can globally change the default behavior of the ``belongs_to`` + association to not require their parent class by setting the + ``belongs_to_required_by_default`` configuration option to ``false`` in your + application's configuration settings. + +You can specify a ``belongs_to`` association in a child class without specifying a matching +``has_one`` or ``has_many`` association in the parent class. When doing so, you +can't access the fields of the child document from the parent class, but you can +access the parent fields that are stored in the child class, such as the +parent's ``_id`` field. In the following example, the ``Band`` +class cannot access the ``Members`` class, but the ``Members`` class can access the +``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-belongs-to-one-way + :end-before: # end-belongs-to-one-way + +For clarity, you can optionally set the ``inverse_of`` option to ``nil`` to +indicate that the parent class does not contain a ``has_one`` or ``has_many`` +association to the child class, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-belongs-to-inverse + :end-before: # end-belongs-to-inverse + +Has and Belongs To Many +~~~~~~~~~~~~~~~~~~~~~~~ + +Use the ``has_and_belongs_to_many`` macro to declare that a class model contains +a many-to-many relationship with another class. In a many-to-many relationship, +each document in one class can be associated with multiple documents in another +class. The following example creates a ``Band`` class with a +``has_and_belongs_to_many`` relationship to a ``Members`` class. A ``Band`` document can +reference multiple ``Members`` documents, and a ``Members`` document can +reference multiple ``Band`` documents. + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-and-belongs-to-many + :end-before: # end-has-and-belongs-to-many + +When you declare a ``has_and_belongs_to_many`` association, both model instances +store a list of the associated document's ``_id`` values. You can set the +``inverse_of`` option to ``nil`` to store the associated document's ``_id`` values in +only one of the model instances. The following example prompts {+odm+} to store +the associated document's ``_id`` values in only the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-has-and-belongs-to-many-inverse + :end-before: # end-has-and-belongs-to-many-inverse + +.. tip:: + + When you update a document that has a ``has_and_belongs_to_many`` association, + {+odm+} sets the ``updated_at`` field of updated document but does not set the + ``updated_at`` field of the associated documents. + +Query Referenced Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use an aggregation pipeline to query for documents across referenced +associations. The aggregation pipeline allows you to create queries across +multiple collections and manipulate data into a specified format. To learn more +about using the aggregation pipeline, see the :ref:`Aggregation +` guide. + +For simple queries, you can query the association directly. When you directly +query on a collection, you can query only on fields and values that are in the +collection itself. You cannot directly query on collections +associated to the one you are querying. + +For example, consider the following ``Band`` and ``Tour`` classes: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-query-models + :end-before: # end-query-models + +The following example queries the ``Tour`` class for documents that have a +``year`` value of ``2000`` or greater and saves the ``band_id`` of those +documents. It then queries the ``Band`` class for documents that have those +``band_id`` values. + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-association-query + :end-before: # end-association-query + +Embedded Associations +--------------------- + +You can use embedded associations to store different types of documents in the +same collection. {+odm+} supports embedded associations with the following +macros: + +- ``embeds_one`` +- ``embeds_many`` +- ``embedded_in`` +- ``recursively_embeds_one`` +- ``recursively_embeds_many`` + +The following sections describe how to use these association types. + +Embeds One +~~~~~~~~~~ + +To specify that a class model contains an embedded document of a different +class type, use the ``embeds_one`` macro in the parent class and the ``embedded_in`` +macro in the embedded class. The following example creates a ``Band`` class with +an embedded ``Label`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embed-one + :end-before: # end-embed-one + +{+odm+} stores documents embedded with the ``embeds_one`` macro in the +parent document as a field with the same name as the embedded class. The +preceding ``Label`` documents are stored in the ``Band`` document, as shown in +the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embed-one-stored + :end-before: # end-embed-one-stored + +You can store the embedded document with a different name by using the +``store_as`` option, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embed-store-as + :end-before: # end-embed-store-as + +Embeds Many +~~~~~~~~~~~ + +To specify that a class model contains multiple embedded documents of a +different class type, use the ``embeds_many`` macro in the parent class and the +``embedded_in`` macro in the embedded class. The following example creates a +``Band`` class with multiple embedded ``Album`` type documents: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embed-many + :end-before: # end-embed-many + +{+odm+} stores documents embedded with the ``embeds_many`` macro in the +parent document as an array field with the same name as the embedded class. The +preceding ``Album`` documents are stored in the ``Band`` document, as shown in +the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embed-many-stored + :end-before: # end-embed-many-stored + +You can store the embedded document with a different name by using the +``store_as`` option, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embed-many-store-as + :end-before: # end-embed-many-store-as + +Recursive Embedding +~~~~~~~~~~~~~~~~~~~ + +You can embed one or more documents of the same type into a parent +class by using the ``recursively_embeds_one`` and ``recursively_embeds_many`` +macros. Both macros provide accessors for the parent and child documents through +a ``parent_*`` method and a ``child_*`` method, where ``*`` represents the name +of the class. The following example creates a ``Band`` class that recursively +embeds multiple other ``Band`` documents to represent multiple band names: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-recursive-embed + :end-before: # end-recursive-embed + +You can access the parent and child documents through the ``parent_band`` and +``child_band`` methods, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-recursive-embed-access + :end-before: # end-recursive-embed-access + +Query Embedded Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can access embedded documents when querying the collection of the parent +class by using dot notation. + +The following example uses dot notation to query ``Tour`` type documents that +are embedded in a ``Band`` class. The query returns documents that have a +``tours.year`` value of ``2000`` or greater: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embedded-query + :end-before: # end-embedded-query + +You can use the ``pluck()`` projection method to retrieve embedded documents +without retrieving their associated parent documents, as shown in the following +example: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embedded-query-pluck + :end-before: # end-embedded-query-pluck + +You can use {+odm+} query methods to perform **embedded matching**, which allows +you to query on embedded associations of documents that are already loaded in +the application. {+odm+} implements embedded matching without sending queries to +the server. + +The following query operators are supported with embedded matching: + +- :manual:`Comparison operators ` +- :manual:`Logical operators ` +- :manual:`Array query operators ` +- :manual:`$exists ` +- :manual:`$mod ` +- :manual:`$type ` +- :manual:`$regex ` +- :manual:`Bitwise operators ` +- :manual:`$comment ` + +The following example queries the embedded ``tours`` field of a loaded ``Band`` +document by using the ``$gte`` comparison operator: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embedded-matching + :end-before: # end-embedded-matching + +Embedded matching on loaded documents has the following known limitations: + +- Embedded matching is not implemented for the following features: + + - :ref:`Text search ` + - :manual:`Geospatial query operators ` + - Operators that execute JavaScript code, such as :manual:`$where ` + - Operators that are implemented through other server functionality, such as + :manual:`$expr ` + and :manual:`$jsonSchema + ` + +- {+odm+} expands ``Range`` arguments to hashes with ``$gte`` and ``$lte`` + conditions. This can lead to invalid queries in some cases and raises a an + ``InvalidQuery`` exception. + +- With the ``$regex`` operator, you cannot specify a regular expression object + as a pattern while also providing options to the ``$options`` field. You can + only provide options if the regular expression pattern is a string. + +- MongoDB Server versions 4.0 and earlier do not strictly validate ``$type`` + arguments. + +Omit _id Fields +~~~~~~~~~~~~~~~ + +By default, {+odm+} adds an ``_id`` field to embedded documents. You can omit +this field from embedded documents by explicitly specifying the ``_id`` field in +your model and omitting the default value. The following example instructs {+odm+} +not to add an ``_id`` field to the ``Albums`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embedded-omit-id + :end-before: # end-embedded-omit-id + :emphasize-lines: 4 + +In the preceding ``Albums`` class, the ``_id`` field is not automatically added. +Without a default value, {+odm+} does not store the value in the database +unless you provide one in your model. + +Delete Embedded Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can delete child documents from ``embeds_many`` associations by using one of +the following methods: + +- ``clear()`` +- ``delete_all()`` +- ``destroy_all()`` + +The ``clear()`` method uses the :manual:`$unset operator +` operator to remove an entire embedded +association from the parent document. The ``clear()`` method does not run any +``destroy`` callbacks. The following example uses the ``clear()`` +method to remove all embedded associations from the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embedded-clear + :end-before: # end-embedded-clear + +The ``delete_all()`` method uses the :manual:`$pullAll operator +` operator to remove documents in an +embedded association. ``delete_all()`` loads the association if it has not +yet been loaded, then only removes the documents that exist in the application. +The ``delete_all()`` method does not run any ``destroy`` callbacks. +The following example uses the ``delete_all()`` method to remove all embedded +``Album`` documents from the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embedded-delete-all + :end-before: # end-embedded-delete-all + +The ``destroy_all()`` method also uses the :manual:`$pullAll operator +` operator to remove documents in an +embedded association. It also runs any ``destroy`` callbacks that are defined on +the associated documents. The following example uses the ``destroy_all()`` +method to remove all embedded ``Album`` documents from the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/associations.rb + :language: ruby + :start-after: # start-embedded-destroy-all + :end-before: # end-embedded-destroy-all diff --git a/source/includes/data-modeling/associations.rb b/source/includes/data-modeling/associations.rb new file mode 100644 index 00000000..b5580d2b --- /dev/null +++ b/source/includes/data-modeling/associations.rb @@ -0,0 +1,299 @@ +# start-has-one +class Band + include Mongoid::Document + + has_one :studio +end +# end-has-one + +# start-has-one-child +class Studio + include Mongoid::Document + + belongs_to :band +end +# end-has-one-child + +# start-has-one-validation +class Band + include Mongoid::Document + + has_one :studio + + validates_presence_of :studio +end +# end-has-one-validation + +# start-has-many +class Band + include Mongoid::Document + + has_many :members +end +# end-has-many + +# start-has-many-child +class Member + include Mongoid::Document + + belongs_to :band +end +# end-has-many-child + +# start-has-many-validation +class Band + include Mongoid::Document + + has_many :members + + validates_presence_of :members +end +# end-has-many-validation + +# start-has-many-any +band = Band.first +band.members.any? +# end-has-many-any + +# start-has-many-any-filter +band = Band.first +band.members.any? { |member| member.instrument == 'piano' } +# end-has-many-any-filter + +# start-has-many-any-class +class Drummer < Member +end + +band = Band.first +band.members.any?(Drummer) +# end-has-many-any-class + +# start-has-many-exists +band = Band.create! +# Member is not persisted. +band.members.build + +band.members.exists? +# Outputs: false + +# Persist the member +band.members.map(&:save!) + +band.members.exists? +# Outputs: true +# end-has-many-exists + +# start-belongs-to +class Members + include Mongoid::Document + + belongs_to :band +end +# end-belongs-to + +# start-belongs-to-optional +class Members + include Mongoid::Document + + belongs_to :band, optional: true +end +# end-belongs-to-optional + +# start-belongs-to-one-way +class Band + include Mongoid::Document +end + +class Members + include Mongoid::Document + + belongs_to :band +end +# end-belongs-to-one-way + +# start-belongs-to-inverse +class Band + include Mongoid::Document +end + +class Members + include Mongoid::Document + + belongs_to :band, inverse_of: nil +end +# end-belongs-to-inverse + +# start-has-and-belongs-to-many +class Band + include Mongoid::Document + + has_and_belongs_to_many :members +end + +class Members + include Mongoid::Document + + has_and_belongs_to_many :bands +end +# end-has-and-belongs-to-many + +# start-has-and-belongs-to-many-inverse +class Band + include Mongoid::Document + + has_and_belongs_to_many :tags, inverse_of: nil +end + +class Tag + include Mongoid::Document +end +# end-has-and-belongs-to-many-inverse + +# start-query-models +class Band + include Mongoid::Document + has_many :tours + + field :name, type: String +end +class Tour + include Mongoid::Document + belongs_to :band + + field :year, type: Integer +end +# end-query-models + +# start-association-query +band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id) +bands = Band.find(band_ids) +# end-association-query + +# start-embed-one +class Band + include Mongoid::Document + + embeds_one :label +end + +class Label + include Mongoid::Document + field :name, type: String + + embedded_in :band +end +# end-embed-one + +# start-embed-one-stored +# Band document +{ + "_id" : ObjectId("..."), + "label" : { + "_id" : ObjectId("..."), + "name" : "Periphery", + } +} +# end-embed-one-stored + +# start-embed-store-as +class Band + include Mongoid::Document + + embeds_one :label, store_as: "record_label" +end +# end-embed-store-as + +# start-embed-many +class Band + include Mongoid::Document + + embeds_many :albums +end + +class Album + include Mongoid::Document + field :name, type: String + + embedded_in :band +end +# end-embed-many + +# start-embed-many-stored +{ + "_id" : ObjectId("..."), + "albums" : [ + { + "_id" : ObjectId("..."), + "name" : "Omega", + } + ] +} +# end-embed-many-stored + +# start-embed-many-store-as +class Band + include Mongoid::Document + + embeds_many :albums, store_as: "records" +end +# end-embed-many-store-as + +# start-recursive-embed +class Band + include Mongoid::Document + field :name, type: String + + recursively_embeds_many +end +# end-recursive-embed + +# start-recursive-embed-access +root = Band.new(name: "Linkin Park") + +# Add child bands +child_one = root.child_band.build(name: "Lincoln Park") +child_two = root.child_band.build(name: "Xero") + +# Access parent band +child_one.parent_band +# Outputs: root +# end-recursive-embed-access + +# start-embedded-query +Band.where('tours.year' => {'$gte' => 2000}) +# end-embedded-query + +# start-embedded-query-pluck +# Get awards for bands that have toured since 2000 +Band.where('tours.year' => {'$gte' => 2000}).pluck(:awards) +# end-embedded-query-pluck + +# start-embedded-matching +band = Band.where(name: 'Astral Projection').first +tours = band.tours.where(year: {'$gte' => 2000}) +# end-embedded-matching + +# start-embedded-omit-id +class Album + include Mongoid::Document + field :name, type: String + field :_id, type: Object + + embedded_in :band +end +# end-embedded-omit-id + +# start-embedded-clear +band = Band.find(...) +band.tours.clear +# end-embedded-clear + +# start-embedded-delete-all +band = Band.find(...) +band.tours.delete_all +# end-embedded-delete-all + +# start-embedded-destroy-all +band = Band.find(...) +band.tours.destroy_all +# end-embedded-destroy-all \ No newline at end of file diff --git a/source/reference/text-search.txt b/source/reference/text-search.txt index b7c3013d..9624affb 100644 --- a/source/reference/text-search.txt +++ b/source/reference/text-search.txt @@ -1,3 +1,4 @@ +.. _mongoid-text-search: .. _text-search: *********** diff --git a/source/reference/validation.txt b/source/reference/validation.txt index a855c006..4ec578ad 100644 --- a/source/reference/validation.txt +++ b/source/reference/validation.txt @@ -1,3 +1,4 @@ +.. _mongoid-validation: .. _validation: ********** From 2c1e2aef1981da42877064a65582bcb6e922feee Mon Sep 17 00:00:00 2001 From: Maya Raman Date: Wed, 18 Dec 2024 11:50:40 -0800 Subject: [PATCH 094/113] DOCSP-45368: Persistence Configuration (#77) --- source/data-modeling.txt | 4 + .../persistence-configuration.txt | 293 ++++++++++++++++++ .../persistence-configuration.rb | 133 ++++++++ .../reference/persistence-configuration.txt | 268 ---------------- 4 files changed, 430 insertions(+), 268 deletions(-) create mode 100644 source/data-modeling/persistence-configuration.txt create mode 100644 source/includes/data-modeling/persistence-configuration.rb delete mode 100644 source/reference/persistence-configuration.txt diff --git a/source/data-modeling.txt b/source/data-modeling.txt index e30a2248..b78317fb 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -17,6 +17,7 @@ Model Your Data Documents Field Types Field Behaviors + Persistence Configuration Inheritance Document Validation Data Associations @@ -32,6 +33,9 @@ In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-field-behaviors`: Learn how to customize the behaviors of fields in {+odm+} to meet your application requirements. + +- :ref:`mongoid-persistence`: Learn how to use {+odm+} to view and customize + your document storage. - :ref:`mongoid-modeling-inheritance`: Learn how to implement inheritance in your model classes. diff --git a/source/data-modeling/persistence-configuration.txt b/source/data-modeling/persistence-configuration.txt new file mode 100644 index 00000000..b281b5d5 --- /dev/null +++ b/source/data-modeling/persistence-configuration.txt @@ -0,0 +1,293 @@ +.. _mongoid-persistence: + +========================= +Persistence Configuration +========================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, customize, config + +.. contents:: On this page + :local: + :backlinks: none + :depth: 3 + :class: singlecol + +Overview +-------- + +In this guide, you can learn about how {+odm+} persists data in your database +and collections. **Persistence configuration** refers to the settings that +control how {+odm+} stores data in MongoDB. This includes the client, +database, and collection where documents for a model class are stored, as +well as other configuration options such as read and write preferences. This guide +provides methods and examples that you can use to access and update +the persistence configuration of a model class. + +.. note:: + + "Client" refers to a host configuration defined under ``clients`` in your + ``mongoid.yml`` file. Most applications use a single client named ``default``. + +Default Collection Name +----------------------- + +By default, {+odm+} stores documents in a collection whose name is the pluralized +form of its representative class name. In the following example, for the +``Restaurant`` class, the corresponding collection is named ``restaurants``. For +the ``Person`` class, the corresponding collection is named ``people``. + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start default modeling + :end-before: end default modeling + +However, the default rules of pluralization don't always work. For +example, suppose your model is named ``Rey``. The plural form of this word in +Spanish is "reyes," but the default collection name is "reys." + +You can create a new pluralization rule for your model class by calling the +`ActiveSupport::Inflector::Inflections.plural() `__ +instance method and passing the singular and plural forms of your class name. +The following example specifies "reyes" as the plural of "rey": + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start set pluralization + :end-before: end set pluralization + +As a result, {+odm+} stores ``Rey`` model class documents in the ``reyes`` +collection. + +.. note:: BSON Document Structure + + When {+odm+} stores a document in a database, it serializes the Ruby object + to a BSON document that has the following structure: + + .. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start BSON model + :end-before: end BSON model + +Persistence Context Attributes +------------------------------ + +Every model class contains the following methods, which you can use to retrieve +information about where {+odm+} persists the model: + +- ``client_name``: Retrieves the client name +- ``database_name``: Retrieves the database name +- ``collection_name``: Retrieves the collection name + +The following example shows how to retrieve and print the names of the client, +database, and collection where documents for the ``Band`` class are persisted: + +.. io-code-block:: + + .. input:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start persistence context attributes + :end-before: end persistence context attributes + + .. output:: + :language: ruby + :visible: false + + default + + my_bands + + bands + +Customize Your Persistence Configuration +---------------------------------------- + +{+odm+} provides both model-level and runtime options for customizing your +persistence configuration. The following sections describe these options. + +Model-Level Persistence Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Suppose you want to store your model's documents in a collection with a +different name than the pluralized form of the model class name. +You can use the ``store_in`` macro to change the collection, database, or client +where {+odm+} stores a model's documents. The following example shows how +to use the ``store_in`` macro to store documents from the ``Person`` class in +a collection called ``citizens`` in the ``other`` database within a client +named ``analytics``: + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start store_in example + :end-before: end store_in example + +The ``store_in`` macro can also accept a lambda function. This is useful if you +want to define a persistence context with values that cannot use a constant string. + +You might want to use this pattern in a multi-tenant application, +where multiple users share common access to an application. By using +a lambda, you can define a persistence context based on information that is local +to the current thread so that users cannot access each others' data. + +The following example stores documents in a database determined by a +thread-local variable: + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start store_in lambda example + :end-before: end store_in lambda example + +.. _mongoid-set-runtime-persistence-options: + +Runtime Persistence Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``with`` method on a model class or instance to change +a model's persistence configuration for a group of operations during runtime. + +Call the ``with`` method on a model class or instance and pass options +that define a persistence context. You can call the ``with`` method in two ways: + +- ``with(context, options)``: ``context`` is an instance of + ``Mongoid::PersistenceContext`` and ``options`` is a Hash that represents a + customizable set of options. + +- ``with(options)``: ``options`` is a Hash that represents a + customizable set of options. + +Then, use a block to define the operations that you want to execute in the +specified context. The context that you define only exists while the code +in the block runs. + +By default, {+odm+} stores documents for the ``Band`` class in a collection called +``bands``. The following code example uses the ``with`` method to temporarily +use a different client, database, and collection to perform operations on the +``Band`` class's documents: + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start with example + :end-before: end with example + +.. note:: Define Clients + + In the preceding example, you must define the ``tertiary`` cluster under + ``clients`` in your ``mongoid.yml`` file. + +.. important:: Block Scope + + You must call the ``with`` method with a block. + This is because {+odm+} uses the options you pass to the method to create + a new client in the background. A block defines the scope of this client + so it can be closed and its resources freed. + +You can also pass the ``with`` method configuration options for read or write +operations. The configurations apply only to the specified type of operation. + +The following example uses the ``with`` method to specify the use of the +secondary node for all read operations within the block. + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start read configuration + :end-before: end read configuration + +.. note:: Ensuring Consistency in Contexts + + When you perform an operation in one context, {+odm+} doesn't automatically + perform the same operation in different contexts. + For example, if you insert a ``Band`` model document into + the ``artists`` collection, the same document will not be inserted into the + ``bands`` collection. + +Global Persistence Contexts ++++++++++++++++++++++++++++ + +In previous examples in this section, you changed persistence context only in +the scope of a block. You can use {+odm+} to globally define a custom persistence +context that all operations in your program use. +This lets you change the persistence context for all operations at runtime +without repeatedly calling the ``with`` method. + +You can use the following methods to globally define the persistence context +in your program: + +- ``{+odm+}.override_client``: {+odm+} performs all operations on the specified client. + +- ``{+odm+}.override_database``: {+odm+} performs all operations on the specified + database. + +In the following code example, the application stores information for different +locales in different databases. The code shows how to use the +``{+odm+}.override_database`` method to globally set the persistence +context based on the locale: + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start global configuration example + :end-before: end global configuration example + +In the preceding example, {+odm+} performs all other operations on this thread +on an alternative database determined by the locale. Because the ``after_action`` +macro sets the override option to ``nil``, subsequent requests with no +changes in persistence configuration use the default configuration. + +Client and Collection Access +---------------------------- + +You can access the client or collection of a model or document instance by using +the ``mongo_client`` and ``collection`` class methods: + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start access client collection + :end-before: end access client collection + +When using these methods, you can also set runtime persistence options by calling +the ``with`` method, similar to examples in the :ref:`mongoid-set-runtime-persistence-options` +section. + +``mongo_client.with`` +~~~~~~~~~~~~~~~~~~~~~ + +The following code example accesses the client used by the ``Band`` model class. +It then uses the ``with`` method on the client to write to the ``music`` +database, setting the ``w`` write option to ``0`` to not require write acknowledgement. + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start client with example + :end-before: end client with example + +``collection.with`` +~~~~~~~~~~~~~~~~~~~ + +You can override the ``:read`` and ``:write`` options on a collection by using the +``with`` method. The following example shows how to use +the ``with`` method to set the ``w`` write option to ``0``: + +.. literalinclude:: /includes/data-modeling/persistence-configuration.rb + :language: ruby + :start-after: start collection with example + :end-before: end collection with example + +API Documentation +----------------- + +For more information about the methods mentioned in this guide, see the following +API documentation: + +- `#client_name <{+api-root+}/PersistenceContext.html#client_name-instance_method>`__ +- `#database_name <{+api-root+}/Clients/Options/ClassMethods.html#database_name-instance_method>`__ +- `#collection_name <{+api-root+}/Clients/Options/ClassMethods.html#collection_name-instance_method>`__ +- `#store_in <{+api-root+}/Clients/StorageOptions/ClassMethods.html#store_in-instance_method>`__ +- `Model.with <{+api-root+}/Clients/Options.html#with-instance_method>`__ +- `Mongoid::PersistenceContext <{+api-root+}/PersistenceContext.html>`__ +- `Mongoid.override_client <{+api-root+}/Config.html#override_client-instance_method>`__ +- `Mongoid.override_database <{+api-root+}/Config.html#override_database-instance_method>`__ +- `Model.mongo_client <{+api-root+}/Clients/Options/ClassMethods.html#mongo_client-instance_method>`__ +- `Model.collection <{+api-root+}/Clients/Options/ClassMethods.html#collection-instance_method>`__ \ No newline at end of file diff --git a/source/includes/data-modeling/persistence-configuration.rb b/source/includes/data-modeling/persistence-configuration.rb new file mode 100644 index 00000000..e6c1ae3f --- /dev/null +++ b/source/includes/data-modeling/persistence-configuration.rb @@ -0,0 +1,133 @@ +# start default modeling +class Restaurant + include Mongoid::Document +end + +class Person + include Mongoid::Document +end +# end default modeling + +# start set pluralization +ActiveSupport::Inflector.inflections do |inflect| + inflect.plural("rey", "reyes") +end +# end set pluralization + +# start BSON model +{ + "_id" : ObjectId("4d3ed089fb60ab534684b7e9"), + "title" : "Sir", + "name" : { + "_id" : ObjectId("4d3ed089fb60ab534684b7ff"), + "first_name" : "Durran" + }, + "addresses" : [ + { + "_id" : ObjectId("4d3ed089fb60ab534684b7e0"), + "city" : "Berlin", + "country" : "Deutschland" + } + ] +} +# end BSON model + +# start store_in example +class Person + include Mongoid::Document + store_in collection: "citizens", database: "other", client: "analytics" +end +# end store_in example + +# start store_in lambda example +class Band + include Mongoid::Document + store_in database: ->{ Thread.current[:database] } +end +# end store_in lambda example + +# start persistence context attributes +puts Band.client_name + +puts Band.database_name + +puts Band.collection_name +# end persistence context attributes + +# start with example +class Band + include Mongoid::Document + + field :name, type: String + field :likes, type: Integer +end + +# Creates document in 'bands' collection in 'music-non-stop' database within +# default cluster +Band.with(database: "music-non-stop") do |band_class| + band_class.create(name: "Medusa and the Polyps") +end + +# Deletes all documents in 'artists' collection within default database +Band.with(collection: "artists") do |band_class| + band_class.delete_all +end + +band = Band.new(name: "Japanese Breakfast") + +# Perform operations on tertiary cluster +band.with(client: :tertiary) do |band_object| + band_object.save! + + band.save! +end +# end with example + +# start read configuration +Band.with(read: {mode: :secondary}) do + Band.count + + # This write operation runs in the + # new persistence context, but is + # not affected by the read preference. + Band.create(name: "Metallica") +end +# end read configuration + +# start global configuration example +class BandsController < ApplicationController + before_action :switch_database + after_action :reset_database + + private + + def switch_database + I18n.locale = params[:locale] || I18n.default_locale + Mongoid.override_database("my_db_name_#{I18n.locale}") + end + + def reset_database + Mongoid.override_database(nil) + end +end +# end global configuration example + +# start access client collection +Band.mongo_client +band.mongo_client + +Band.collection +band.collection +# end access client collection + +# start client with example +Band.mongo_client.with(write: { w: 0 }, database: "music") do |client| + client[:artists].find(...) +end +# end client with example + +# start collection with example +Band.collection.with(write: { w: 0 }) do |collection| + collection[:artists].find(...) +end +# end collection with example \ No newline at end of file diff --git a/source/reference/persistence-configuration.txt b/source/reference/persistence-configuration.txt deleted file mode 100644 index 6712fb55..00000000 --- a/source/reference/persistence-configuration.txt +++ /dev/null @@ -1,268 +0,0 @@ -.. _persistence: - -************************* -Persistence Configuration -************************* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -Document Storage -================ - -Mongoid by default stores documents in a collection that is the pluralized form of the class name. -For the following ``Person`` class, the collection the document would get stored in would be named ``people``. - -.. code-block:: ruby - - class Person - include Mongoid::Document - end - -Model class names cannot end with "s", because it will be considered as the pluralized form of -the word. For example "Status" would be considered as the plural form of "Statu", -which will cause a few known problems. - -This is a limitation of the ``ActiveSupport::Inflector#classify`` which Mongoid uses to convert -from filenames and collection names to class names. You can overcome this by specifying a custom -inflection rule for your model class. For example, the following code will take care of the model -named ``Status``. - -.. code-block:: ruby - - ActiveSupport::Inflector.inflections do |inflect| - inflect.singular("status", "status") - end - -The collection for the model's documents can be changed at the class level if you would like -them persisted elsewhere. You can also change the database and client the model gets persisted -in from the defaults. - -.. code-block:: ruby - - class Person - include Mongoid::Document - store_in collection: "citizens", database: "other", client: "analytics" - end - -The ``store_in`` macro can also take lambdas - a common case for this is multi-tenant applications. - -.. code-block:: ruby - - class Band - include Mongoid::Document - store_in database: ->{ Thread.current[:database] } - end - -When a document is stored in the database the ruby object will get serialized into BSON -and have a structure like so: - -.. code-block:: ruby - - { - "_id" : ObjectId("4d3ed089fb60ab534684b7e9"), - "title" : "Sir", - "name" : { - "_id" : ObjectId("4d3ed089fb60ab534684b7ff"), - "first_name" : "Durran" - }, - "addresses" : [ - { - "_id" : ObjectId("4d3ed089fb60ab534684b7e0"), - "city" : "Berlin", - "country" : "Deutschland" - } - ] - } - - -Persistence Context Attributes -============================== - -Mongoid provides ``client_name``, ``database_name`` and ``collection_name`` -methods on model classes to determine the client, database and collection names -used for persistence: - -.. code-block:: ruby - - Band.client_name - # => :default - - Band.database_name - # => "mongoid" - - Band.collection_name - # => :bands - - -Custom -====== - -There may be cases where you want to persist documents to different sources from their -defaults, or with different options from the default. Mongoid provides run-time support -for this as well as support on a per-model basis. - - -Model-Level Persistence Options -------------------------------- - -On a per-model basis, you can tell it to store in a custom collection name, a different -database, or a different client. The following example would store the Band class by -default into a collection named "artists" in the database named "music", with the client "analytics". - -Note that the value supplied to the ``client`` option must be configured under ``clients`` -in your mongoid.yml. - -.. code-block:: ruby - - class Band - include Mongoid::Document - store_in collection: "artists", database: "music", client: "analytics" - end - -If no ``store_in`` macro would have been provided, Mongoid would store the model in a -collection named "bands" in the default database in the default client. - - -Runtime Persistence Options ---------------------------- - -It is possible to change the client, database and collection, as well as -any of the MongoDB client options, used for persistence for a group of -operations by using the ``with`` method on a model class or instance: - -.. code-block:: ruby - - Band.with(database: "music-non-stop") do |klass| - klass.create(...) - - band = Band.first - - Band.create(...) - end - - Band.with(collection: "artists") do |klass| - klass.delete_all - - Band.delete_all - end - - band.with(client: :tertiary) do |band_object| - band_object.save! - - band.save! - end - -The ``with`` method creates a temporary persistence context and a MongoDB -client to be used for operations in the context. For the duration of the block, -the persistence context on the model class or instance that ``with`` was -called on is changed to the temporary persistence context. For convenience, -the model class or instance that ``with`` was called on is yielded to the -block. - -The temporary persistence context applies to both queries and writes. - -Care should be taken when performing persistence operations across different -persistence contexts. For example, if a document is saved in a temporary -persistence context, it may not exist in the default persistence context, -failing subsequent updates: - -.. code-block:: ruby - - band = Band.new(name: "Scuba") - band.with(collection: "artists") do |band_object| - band_object.save! - end - - # This will not save - updates the collection "bands" which does not have - # the Scuba band - band.update_attribute(likes: 1000) - - # This will update the document. - band.with(collection: "artists") do |band_object| - band_object.update_attribute(likes: 1000) - end - -As of Mongoid 6.0, the ``with`` method must always be called with a block, -and the temporary persistence context exists only for the duration of the block. -This is because a new client is created under the covers with the options -passed to ``with``. To ensure that this client is closed and its associated -resources are freed, the scope when this client could be used must be -well-defined. - - -Global Override -``````````````` - -If you want to switch the persistence context for all operations at runtime, but don't want -to be using with all over your code, Mongoid provides the ability to do this as the client -and database level globally. The methods for this are ``Mongoid.override_client`` and -``Mongoid.override_database``. A useful case for this are internationalized applications -that store information for different locales in different databases or clients, but the -schema in each remains the same. - -.. code-block:: ruby - - class BandsController < ApplicationController - before_action :switch_database - after_action :reset_database - - private - - def switch_database - I18n.locale = params[:locale] || I18n.default_locale - Mongoid.override_database("my_db_name_#{I18n.locale}") - end - - def reset_database - Mongoid.override_database(nil) - end - end - -In the above example, all persistence operations would be stored in the alternative -database for all remaining operations on this thread. This is why the after request -set the override back to nil - it ensures subsequent requests with no local params -use the default option. - -Persistence context applies to both read and write operations. For example, -secondary reads can be performed as follows: - -.. code-block:: ruby - - Band.with(read: {mode: :secondary}) do - Band.count - end - - -Client and Collection Access ----------------------------- - -If you want to drop down to the driver level to perform operations, you can grab -the Mongo client or collection from the model or document instance: - -.. code-block:: ruby - - Band.mongo_client - band.mongo_client - Band.collection - band.collection - -From here you also have the same runtime persistence options using the client's ``#with``: - -.. code-block:: ruby - - client = Band.mongo_client.with(write: { w: 0 }, database: "musik") - client[:artists].find(...) - -You can also override the :read or :write options on the collection using the collections ``#with``: - -.. code-block:: ruby - - collection_w_0 = Band.collection.with(write: { w: 0 }) - collection_w_0[:artists].find(...) From 39bdd5bde2afdb57282e0aab162148356a8db6e1 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:11:44 -0500 Subject: [PATCH 095/113] DOCSP-45361: callbacks (#75) * DOCSP-45361: callbacks * wip * wip * wip * NR PR fixes 1 --- snooty.toml | 1 + source/add-existing.txt | 4 +- source/data-modeling.txt | 4 + source/data-modeling/callbacks.txt | 178 +++++++++++++++++++++ source/data-modeling/validation.txt | 8 +- source/includes/data-modeling/callbacks.rb | 78 +++++++++ source/quick-start-rails.txt | 2 +- 7 files changed, 268 insertions(+), 7 deletions(-) create mode 100644 source/data-modeling/callbacks.txt create mode 100644 source/includes/data-modeling/callbacks.rb diff --git a/snooty.toml b/snooty.toml index d3adf1c5..8ea40b05 100644 --- a/snooty.toml +++ b/snooty.toml @@ -31,3 +31,4 @@ server-manual = "Server manual" api-root = "https://www.mongodb.com/docs/mongoid/master/api/Mongoid" api = "https://www.mongodb.com/docs/mongoid/master/api" ruby-api = "https://www.mongodb.com/docs/ruby-driver/current/api/Mongo" +active-record-docs = "https://guides.rubyonrails.org" diff --git a/source/add-existing.txt b/source/add-existing.txt index ae75633a..fdd6fde2 100644 --- a/source/add-existing.txt +++ b/source/add-existing.txt @@ -130,10 +130,10 @@ for each Rails component, as shown in the following sample .. note:: Because they rely on Active Record, the `ActionText - `__, + <{+active-record-docs+}/action_text_overview.html>`__, `ActiveStorage `__, and `ActionMailbox - `__ + <{+active-record-docs+}/action_mailbox_basics.html>`__ adapters cannot be used alongside {+odm+}. Disable Active Record Adapters diff --git a/source/data-modeling.txt b/source/data-modeling.txt index b78317fb..90fa06cd 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -20,6 +20,7 @@ Model Your Data Persistence Configuration Inheritance Document Validation + Callbacks Data Associations Optimize Queries With Indexes @@ -43,6 +44,9 @@ In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-modeling-validation`: Learn how to create document validation rules for your model classes. +- :ref:`mongoid-modeling-callbacks`: Learn how to implement callbacks to + customize the life cycle of your models. + - :ref:`mongoid-associations`: Learn how to create and manage data associations in your model classes. diff --git a/source/data-modeling/callbacks.txt b/source/data-modeling/callbacks.txt new file mode 100644 index 00000000..0eb74517 --- /dev/null +++ b/source/data-modeling/callbacks.txt @@ -0,0 +1,178 @@ +.. _mongoid-modeling-callbacks: + +========= +Callbacks +========= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, code example, life cycle + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to implement **callbacks** in your +{+odm+} models to customize the life cycle of your model instances. + +Callbacks are methods that {+odm+} triggers at specified moments of +an object's life cycle. They allow you to initiate specified actions +before or after changes to an object's state. + +{+odm+} implements many of the callbacks from Active Record. To learn +more, see `Callbacks +<{+active-record-docs+}/active_record_callbacks.html>`__ in the +Active Record documentation. + +Supported Callbacks +------------------- + +Mongoid supports the following callbacks on model classes that implement +the :ref:`Document ` module: + +- ``after_initialize`` +- ``after_build`` +- ``before_validation`` +- ``after_validation`` +- ``before_create`` +- ``around_create`` +- ``after_create`` +- ``after_find`` +- ``before_update`` +- ``around_update`` +- ``after_update`` +- ``before_upsert`` +- ``around_upsert`` +- ``after_upsert`` +- ``before_save`` +- ``around_save`` +- ``after_save`` +- ``before_destroy`` +- ``around_destroy`` +- ``after_destroy`` + +To learn more about any of the preceding callback types, see the +`ActiveRecord::Callbacks +`__ +reference in the Rails API documentation. + +You can implement callbacks in both top-level and embedded document +models. + +.. note:: Callback Invocation Behavior + + For efficiency, {+odm+} invokes the callback only on the document + that you performed the persistence action on. This behavior enables + {+odm+} to support large hierarchies and handle optimized atomic + updates efficiently by not invoking callbacks throughout the document + hierarchy. + +Take precautions and ensure testability when implementing callbacks for +domain logic, because these designs can lead to unexpected errors when +callbacks in the chain halt execution. We recommend using callbacks for +cross-cutting concerns outside of your program's core functionality, +such as queueing up background jobs. + +Document Callbacks +------------------ + +You must implement and register callbacks on your model classes. +You can register a callback by using ordinary methods, blocks and +``Proc`` objects, or by defining custom callback objects that use +classes or modules. + +This example demonstrates how to register callbacks on the ``Contact`` +model class in the following ways: + +- Includes the ``before_save`` class method, which triggers the + ``process_phone`` method before a ``Contact`` instance is saved to + MongoDB. The ``process_phone`` method is defined separately in the class. + +- Includes the ``after_destroy`` class method and uses a block to print a + message when a ``Contact`` instance is deleted. + +.. literalinclude:: /includes/data-modeling/callbacks.rb + :start-after: start-doc-callback + :end-before: end-doc-callback + :language: ruby + :emphasize-lines: 8, 11-13, 16-18 + :dedent: + +The following code performs data operations that demonstrate the +callback actions: + +.. literalinclude:: /includes/data-modeling/callbacks.rb + :start-after: start-doc-ops + :end-before: end-doc-ops + :language: ruby + :dedent: + +Because callback functionality comes from Active Support, you can +alternatively use the ``set_callback`` class method syntax to register +callbacks. The following code demonstrates how to use this syntax to +create a callback that stores original values of the ``name`` field in +the ``aliases`` array: + +.. literalinclude:: /includes/data-modeling/callbacks.rb + :start-after: start-doc-set-syntax + :end-before: end-doc-set-syntax + :language: ruby + :emphasize-lines: 8-12 + :dedent: + +Association Callbacks +--------------------- + +{+odm+} provides the following association callbacks: + +- ``after_add`` +- ``after_remove`` +- ``before_add`` +- ``before_remove`` + +If you register an association callback on your model class, it is +invoked whenever you add or remove a document from any of the following +associations: + +- ``embeds_many`` +- ``has_many`` +- ``has_and_belongs_to_many`` + +Specify association callbacks as options on the respective association. +You must pass the added or removed document as the parameter to the +specified callback. + +The following code demonstrates how to register an association callback +on a ``User`` model class that embeds multiple ``SavedArticle`` +instances to limit the number of embedded documents for a single +instance: + +.. literalinclude:: /includes/data-modeling/callbacks.rb + :start-after: start-association-callback + :end-before: end-association-callback + :language: ruby + :emphasize-lines: 6, 10-15 + :dedent: + +Additional Information +---------------------- + +To learn how to prevent {+odm+} from running callbacks, see the +following references in the Active Record documentation: + +- `Skipping Callbacks <{+active-record-docs+}/active_record_callbacks.html#skipping-callbacks>`__ +- `Suppressing Saving <{+active-record-docs+}/active_record_callbacks.html#suppressing-saving>`__ + +To learn about how {+odm+} manages callbacks in transactions, see the +:ref:`mongoid-data-txn` guide. + +To learn how to access and change your MongoDB data, see the +:ref:`mongoid-interact-data` guides. diff --git a/source/data-modeling/validation.txt b/source/data-modeling/validation.txt index 9f96c72f..5d87a0a2 100644 --- a/source/data-modeling/validation.txt +++ b/source/data-modeling/validation.txt @@ -29,7 +29,7 @@ of document fields in your collections. {+odm+} includes ``ActiveModel::Validations`` from Active Record to provide validation functionality, including an associated and uniqueness validator. To learn more, see the `Active Record Validations -`__ +<{+active-record-docs+}/active_record_validations.html>`__ Rails guide and `ActiveModel::Validations `__ Rails API documentation. @@ -284,14 +284,14 @@ Custom Validation Rules You can use the ``validates_each`` and ``validates_with`` helpers to create custom validators. To learn more about these helpers and view examples, see the `validates_each -`__ +<{+active-record-docs+}/active_record_validations.html#validates-each>`__ and `validates_with -`__ +<{+active-record-docs+}/active_record_validations.html#validates-with>`__ references in the Active Record documentation. To learn more about custom validators, see `Performing Custom Validations -`__ +<{+active-record-docs+}/active_record_validations.html#performing-custom-validations>`__ in the Active Record documentation. Behavior diff --git a/source/includes/data-modeling/callbacks.rb b/source/includes/data-modeling/callbacks.rb new file mode 100644 index 00000000..c48e04d5 --- /dev/null +++ b/source/includes/data-modeling/callbacks.rb @@ -0,0 +1,78 @@ +# start-doc-callback +class Contact + include Mongoid::Document + + field :name, type: String + field :phone, type: String + + # Creates a callback to clean phone numbers before saving + before_save :process_phone + + protected + def process_phone + self.phone = phone.gsub(/[^0-9]/, "") if attribute_present?("phone") + end + + # Creates a callback to send a message about object deletion + after_destroy do + p "deleted the contact for #{name}" + end +end +# end-doc-callback + +# start-doc-ops +Contact.create(name: 'Serena Atherton', phone: '999 555-3030') +# => `phone` field saved as '9995553030' +Contact.create(name: 'Zayba Haq', phone: '999 123?5050') +# => `phone` field saved as '9991235050' + +Contact.first.destroy +# => Console message: "deleted the contact for Serena Atherton" +# end-doc-ops + +# start-doc-set-syntax +class Contact + include Mongoid::Document + + field :name, type: String + field :phone, type: String + field :aliases, type: Array, default: [] + + set_callback(:update, :before) do |document| + if document.name_changed? + document.push(aliases: document.name_was) + end + end +end + +Contact.create(name: 'Xavier Bloom', phone: '4447779999') +Contact.first.update(name: 'Xav - coworker') +# Saved document in MongoDB: +# {"aliases":["Xavier Bloom"],"name":"Xav - coworker","phone":"4447779999"} +# end-doc-set-syntax + +# start-association-callback +class User + include Mongoid::Document + + field :username, type: String + # Registers the callback in the association statement + embeds_many :saved_articles, before_add: :send_message + + protected + # Passes the association document as a parameter to the callback + def send_message(saved_article) + if saved_articles.count >= 10 + p "you can't save more than 10 articles at a time" + throw(:abort) + end + end +end + +class SavedArticle + include Mongoid::Document + embedded_in :user + + field :url, type: String +end +# end-association-callback \ No newline at end of file diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index 8f3dc972..60de78da 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -45,7 +45,7 @@ modeled and displayed. {+odm+} replaces the default Active Record adapter for data modeling in Rails. To learn more about Ruby on Rails, see the `Getting Started -with Rails `__ +with Rails <{+active-record-docs+}/getting_started.html>`__ guide in the Rails documentation. MongoDB Atlas is a fully managed cloud database service that hosts your From 66c57266d292c430c9ce9bf86f648bd9971f957f Mon Sep 17 00:00:00 2001 From: Maya Raman Date: Fri, 10 Jan 2025 14:01:13 -0500 Subject: [PATCH 096/113] DOCSP-45364: CRUD pt 1 (#81) * checkpoint * checkpoint 2 * woohoo first pass * indent * Edits * updates * vale chekcs * RR PR fixes 1 * fix code file * code fixes * RM PR fixes 1 --------- Co-authored-by: rustagir --- source/data-modeling/validation.txt | 2 +- source/includes/interact-data/crud.rb | 266 ++++++++++ source/interact-data.txt | 4 + source/interact-data/crud.txt | 690 ++++++++++++++++++++++++++ 4 files changed, 961 insertions(+), 1 deletion(-) create mode 100644 source/includes/interact-data/crud.rb create mode 100644 source/interact-data/crud.txt diff --git a/source/data-modeling/validation.txt b/source/data-modeling/validation.txt index 5d87a0a2..6909df02 100644 --- a/source/data-modeling/validation.txt +++ b/source/data-modeling/validation.txt @@ -305,7 +305,7 @@ database. The following methods trigger your validation rules, so - ``save()`` - ``update()`` -When you use the ``-!`` suffixed version of the preceding methods, +When you use the ``!``-suffixed version of the preceding methods, {+odm+} returns an ``Mongoid::Errors::Validations`` exception if validation fails for an object. diff --git a/source/includes/interact-data/crud.rb b/source/includes/interact-data/crud.rb new file mode 100644 index 00000000..85dbc78b --- /dev/null +++ b/source/includes/interact-data/crud.rb @@ -0,0 +1,266 @@ +# start create! example +Person.create!( + first_name: "Heinrich", + last_name: "Heine" +) + +Person.create!([ + { first_name: "Heinrich", last_name: "Heine" }, + { first_name: "Willy", last_name: "Brandt" } +]) + +Person.create!(first_name: "Heinrich") do |doc| + doc.last_name = "Heine" +end +# end create! example + +# start create example +Person.create( + first_name: "Heinrich", + last_name: "Heine" +) + +class Post + include Mongoid::Document + validates_uniqueness_of :title +end + +posts = Post.create([{title: "test"}, {title: "test"}]) +posts.map { |post| post.persisted? } # => [true, false] +# end create example + +# start save! example +person = Person.new( + first_name: "Esmeralda", + last_name: "Qemal" +) +person.save! + +person.first_name = "Malik" +person.save! +# end save! example + +# start save example +person = Person.new( + first_name: "Tamara", + last_name: "Graham" +) +person.save + +person.first_name = "Aubrey" +person.save(validate: false) +# end save example + +# start attributes example +person = Person.new(first_name: "James", last_name: "Nan") +person.save + +puts person.attributes +# end attributes example + +# start reload example +band = Band.create!(name: 'Sun 1') +# => # + +band.name = 'Moon 2' +# => # + +band.reload +# => # +# end reload example + +# start reload unsaved example +existing = Band.create!(name: 'Photek') + +band = Band.new(id: existing.id) +band.reload + +puts band.name +# end reload unsaved example + +# start update attributes! example +person.update_attributes!( + first_name: "Maximilian", + last_name: "Hjalmar" +) +# end update attributes! example + +# start update attributes example +person.update_attributes( + first_name: "Hasan", + last_name: "Emine" +) +# end update attributes example + +# start update attribute example +person.update_attribute(:first_name, "Jean") +# end update attribute example + +# start upsert example +person = Person.new( + first_name: "Balu", + last_name: "Rama" +) +person.upsert + +person.first_name = "Ananda" +person.upsert(replace: true) +# end upsert example + +# start touch example +person.touch(:audited_at) +# end touch example + +# start delete example +person = Person.create!(name: 'Edna Park') + +unsaved_person = Person.new(id: person.id) +unsaved_person.delete +person.reload +# end delete example + +# start destroy example +person.destroy +# end destroy example + +# start delete all example +Person.delete_all +# end delete all example + +# start destroy all example +Person.destroy_all +# end destroy all example + +# start new record example +person = Person.new( + first_name: "Tunde", + last_name: "Adebayo" +) +puts person.new_record? + +person.save! +puts person.new_record? +# end new record example + +# start persisted example +person = Person.new( + first_name: "Kiana", + last_name: "Kahananui" +) +puts person.persisted? + +person.save! +puts person.persisted? +# end persisted example + +# start field values default +class Person + include Mongoid::Document + field :first_name +end + +person = Person.new + +person.first_name = "Artem" +person.first_name # => "Artem" +# end field values default + +# start field values hash +class Person + include Mongoid::Document + + field :first_name, as: :fn +end + +person = Person.new(first_name: "Artem") + +person["fn"] +# => "Artem" + +person[:first_name] = "Vanya" +# => "Artem" + +person +# => # +# end field values hash + +# start read write attributes +class Person + include Mongoid::Document + + def first_name + read_attribute(:fn) + end + + def first_name=(value) + write_attribute(:fn, value) + end +end + +person = Person.new + +person.first_name = "Artem" +person.first_name +# => "Artem" +# end read write attributes + +# start read write instance +class Person + include Mongoid::Document + field :first_name, as: :fn +end + +person = Person.new(first_name: "Artem") +# => # + +person.read_attribute(:first_name) +# => "Artem" + +person.read_attribute(:fn) +# => "Artem" + +person.write_attribute(:first_name, "Pushkin") + +person +# => # +# end read write instance + +# start attributes= example +person.attributes = { first_name: "Jean-Baptiste", middle_name: "Emmanuel" } +# end attributes= example + +# start write_attributes example +person.write_attributes( + first_name: "Jean-Baptiste", + middle_name: "Emmanuel", +) +# end write_attributes example + +# start atomically example +person.atomically do + person.inc(age: 1) + person.set(name: 'Jake') +end +# end atomically example + +# start default block atomic example +person.atomically do + person.atomically do + person.inc(age: 1) + person.set(name: 'Jake') + end + raise 'An exception' + # Name and age changes are persisted +end +# end default block atomic example + +# start join_contexts atomic +person.atomically do + person.atomically(join_context: true) do + person.inc(age: 1) + person.set(name: 'Jake') + end + raise 'An exception' + # Name and age changes are not persisted +end +# end join_contexts atomic \ No newline at end of file diff --git a/source/interact-data.txt b/source/interact-data.txt index 2e3e3707..f211ae5d 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -14,6 +14,7 @@ Interact with Data .. toctree:: :caption: Interact with Data + Perform Data Operations Specify a Query Modify Query Results Search Text @@ -23,6 +24,9 @@ Interact with Data In this section, you can learn how to use {+odm+} to interact with your MongoDB data. +- :ref:`mongoid-data-crud`: Learn how to create, read, update, and + delete documents in a collection. + - :ref:`mongoid-data-specify-query`: Learn how to construct queries to match specific documents in a MongoDB collection. diff --git a/source/interact-data/crud.txt b/source/interact-data/crud.txt new file mode 100644 index 00000000..41eede34 --- /dev/null +++ b/source/interact-data/crud.txt @@ -0,0 +1,690 @@ +.. _mongoid-data-crud: + +======================= +Perform Data Operations +======================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, create data, edit + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to use {+odm+} to perform CRUD (create, +read, update, delete) operations to modify the data in your MongoDB +collections. + +{+odm+} supports CRUD operations that you can perform by using other +{+language+} mappers such as Active Record or Data Mapper. +When using {+odm+}, general persistence operations perform atomic +updates on only the fields that you change instead of writing the +entire document to the database each time, as is the case with other +ODMs. + +Create Operations +----------------- + +You can perform create operations to add new documents to a collection. If +the collection doesn't exist, the operation implicitly creates the +collection. The following sections describe the methods you can use to +create new documents. + +create! +~~~~~~~ + +Use the ``create!`` method on your model class to insert one or more +documents into a collection. If any server or validation errors occur, +``create!`` raises an exception. + +To call ``create!``, pass a hash of attributes that define the document +you want to insert. If you want to create and insert multiple documents, +pass an array of hashes. + +This example shows multiple ways to call ``create!``. The first example +creates one ``Person`` document, and the second example creates two ``Person`` +documents. The third example passes a ``do..end`` block to ``create!``. {+odm+} +invokes this block with the documents passed to ``create!`` as +arguments. The ``create!`` method attempts to save the document at the +end of the block: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start create! example + :end-before: end create! example + +create +~~~~~~ + +Use the ``create`` method to insert a new document or multiple new documents into a +database. ``create`` does not raise an exception on validation errors, +unlike the ``!``-suffixed version. ``create`` does raise exceptions on +server errors, such as if you insert a document with a duplicate ``_id`` field. + +If ``create`` encounters any validation errors, the document is not inserted +but is returned with other documents that were inserted. You can use the +``persisted?``, ``new_record?`` or ``errors`` methods to verify the +documents that were inserted into the database. + +This example shows how to use ``create`` to insert new documents +into MongoDB. The first example shows how to insert a ``Person`` +document. The second example attempts to insert two ``Post`` documents, +but the second document fails validation because it contains a duplicate +title. The example then uses the ``persisted?`` method to confirm which +documents were successfully inserted into the collection: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start create example + :end-before: end create example + +To learn more about the ``persisted?`` and ``new_record?`` methods, see +the :ref:`mongoid-persistence-attr` section of this guide. + +save! +~~~~~ + +Use the ``save!`` method to atomically save changed attributes to the collection or +to insert a new document. ``save!`` raises an exception if there are any +server or validation errors. You can use the ``new`` method to create a new document +instance. Then, use ``save!`` to insert the document into the database. + +The following example shows how to use ``save!`` to insert a new ``Person`` +document and update the ``first_name`` field of the existing document: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start save! example + :end-before: end save! example + +save +~~~~ + +The ``save`` method does not raise an exception if there are any +validation errors. ``save`` still raises an exception if there are any +server errors. The method returns ``true`` if all changed attributes +are saved, and ``false`` if any validation errors occur. + +You can pass the following options to ``save``: + +- ``validate: false``: To bypass validations when saving the new + document or updated attributes. + +- ``touch: false``: To not update the ``updated_at`` field when + updating the specified attributes. This option has no effect when + inserting a new document. + +The following code uses ``save`` to insert a new document. It then updates +that document and applies the ``validate: false`` option. + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start save example + :end-before: end save example + +Read Operations +--------------- + +You can perform read operations to retrieve documents from a collection. + +.. _mongoid-read-attributes: + +attributes +~~~~~~~~~~ + +You can use the ``attributes`` method to retrieve the attributes of a +model instance as a hash. This hash also contains the attributes of all +embedded documents. + +The following example shows how to use ``attributes``: + +.. io-code-block:: + + .. input:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start attributes example + :end-before: end attributes example + + .. output:: + :language: console + + { "_id" => BSON::ObjectId('...'), + "first_name" => "James", + "last_name" => "Nan" + } + +reload +~~~~~~ + +You can use the ``reload`` method to access the most recent version of a +document from MongoDB. When you reload a document, {+odm+} also reloads any embedded +associations in the same query. However, {+odm+} does not reload +referenced associations. Instead, it clears these values so that they +are loaded from the database during the next access. + +When you call ``reload`` on a document, any unsaved changes to the document +are lost. The following code shows how to call ``reload`` on a document: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start reload example + :end-before: end reload example + +The preceding example updates the ``name`` field on the ``band`` document, +but does not save the new value. Because {+odm+} did not persist the +change to the ``name`` value, ``name`` contains the original value saved +to the database. + +.. note:: Document Not Found Errors + + When {+odm+} cannot find a document in the database, by default it raises a + ``Mongoid::Errors::DocumentNotFound`` error. You can set the + ``raise_not_found_error`` configuration option to ``false`` in your ``mongoid.yml`` + file to direct {+odm+} to save a new document and set its attributes to + default values. Generally, it also changes the value of the ``_id`` + field. For this reason, we do not recommend using ``reload`` when + ``raise_not_found_error`` is set to ``false``. + +Reload Unsaved Documents +++++++++++++++++++++++++ + +When you call ``reload`` on a document that is not persisted, the method performs +a ``find`` query on the document's ``_id`` value. + +The following example calls ``reload`` on a document that has not been +saved and prints out the ``name`` field value. ``reload`` performs a +``find`` operation using the document's ``_id`` value, which causes +{+odm+} to retrieve the existing document in the collection: + +.. io-code-block:: + :copyable: true + + .. input:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start reload unsaved example + :end-before: end reload unsaved example + + .. output:: + :visible: false + + Photek + +Update Operations +----------------- + +You can perform update operations to modify existing documents in a +collection. If you attempt to update a deleted document, {+odm+} raises +a ``FrozenError`` exception. + +update_attributes! +~~~~~~~~~~~~~~~~~~ + +You can use the ``update_attributes!`` method to update the attributes of an +existing model instance. This method raises an exception if it encounters +any validation or server errors. + +The following example shows how to use ``update_attributes!`` to update +the ``first_name`` and ``last_name`` attributes of an existing document: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start update attributes! example + :end-before: end update attributes! example + +.. tip:: + + {+odm+} provides the nested attributes feature that allows you to + update a document and its nested associations in one call. To learn + more, see the :ref:`mongoid-data-nested-attr` guide. + +update_attributes +~~~~~~~~~~~~~~~~~ + +The ``update_attributes`` method does not raise an exception on +validation errors. The method returns ``true`` if it passes validation +and the document is updated, and ``false`` otherwise. + +The following example shows how to use ``update_attributes``: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start update attributes example + :end-before: end update attributes example + +update_attribute +~~~~~~~~~~~~~~~~ + +You can use the ``update_attribute`` method to bypass validations and +update a *single* attribute of a model instance. + +The following example shows how to use ``update_attribute`` to update +the value of a document's ``first_name`` attribute: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start update attribute example + :end-before: end update attribute example + +upsert +~~~~~~ + +You can use the ``upsert`` method to update, insert, or replace a +document. + +``upsert`` accepts a ``replace`` option. If you set this option to ``true`` +and the document that calls ``upsert`` already exists in the database, +the new document replaces the one in the database. Any fields in the +database that the new document does not replace are removed. + +If you set the ``replace`` option to ``false`` and the document exists in the +database, it is updated. {+odm+} does not change any fields other than the +ones specified in the update document. If the document does not exist in the +database, it is inserted with the fields and values specified in the +update document. The ``replace`` option is set to ``false`` by default. + +The following example shows how to use ``upsert`` to first insert a new +document, then replace it by setting ``replace: true``: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start upsert example + :end-before: end upsert example + +touch +~~~~~ + +You can use the ``touch`` method to update a document's ``updated_at`` +timestamp to the current time. ``touch`` cascades the update to any of +the document's ``belongs_to`` associations. You can also pass another +time-valued field as an option to also update that field. + +The following example uses ``touch`` to update the +``updated_at`` and ``audited_at`` timestamps: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start touch example + :end-before: end touch example + +Delete Operations +----------------- + +You can perform delete operations to remove documents from a collection. + +delete +~~~~~~ + +You can use the ``delete`` method to delete a document from the database. When you +use ``delete``, {+odm+} does not run any callbacks. If the document is not +saved to the database, ``delete`` attempts to delete any document with +the same ``_id`` value. + +The following example shows how to use the ``delete`` method and +demonstrates what happens when you delete a document that is not saved +to the database: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start delete example + :end-before: end delete example + +In the preceding example, {+odm+} raises a ``Mongoid::Errors::DocumentNotFound`` +error when you call ``reload`` because ``unsaved_person.delete`` deletes +the ``person`` document because the two documents have the same value +for ``_id``. + +destroy +~~~~~~~ + +The ``destroy`` method operates similarly to ``delete``, except {+odm+} +runs callbacks when you call ``destroy``. If the document is not found +in the database, ``destroy`` attempts to delete any document with +the same ``_id``. + +The following example shows how to use ``destroy``: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start destroy example + :end-before: end destroy example + +delete_all +~~~~~~~~~~ + +The ``delete_all`` method deletes all documents from the collection that +are modeled by your {+odm+} model class. ``delete_all`` does not run +callbacks. + +The following example shows how to use ``delete_all`` to delete all +``Person`` documents: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start delete all example + :end-before: end delete all example + +destroy_all +~~~~~~~~~~~ + +The ``destroy_all`` method deletes all documents from the collection +that are modeled by your {+odm+} model class. This can be an expensive +operation because {+odm+} loads all documents into memory. + +The following example shows how to use ``destroy_all`` to delete all +``Person`` documents: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start destroy all example + :end-before: end destroy all example + +.. _mongoid-persistence-attr: + +Persistence Attributes +---------------------- + +The following sections describe the attributes that {+odm+} provides that +you can use to check if a document is persisted to the database. + +new_record? +~~~~~~~~~~~ + +The ``new_record?`` attribute returns ``true`` if the model instance is +*not saved* to the database yet, and ``false`` otherwise. It checks for +the opposite condition as the ``persisted?`` attribute. + +The following example shows how to use ``new_record?``: + +.. io-code-block:: + + .. input:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start new record example + :end-before: end new record example + + .. output:: + :visible: false + + true + + false + +persisted? +~~~~~~~~~~ + +The ``persisted?`` attribute returns ``true`` if {+odm+} persists the +model instance, and ``false`` otherwise. It checks for the opposite +condition as the ``new_record?`` attribute. + +The following example shows how to use ``persisted?``: + +.. io-code-block:: + + .. input:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start persisted example + :end-before: end persisted example + + .. output:: + :visible: false + + false + + true + +Access Field Values +------------------- + +{+odm+} provides several ways to access field values on a document. The +following sections describe how you can access field values. + +Get and Set Field Values +~~~~~~~~~~~~~~~~~~~~~~~~ + +There are multiple ways to get and set field values on a document. If you explicitly +declare a field, you can get and set this field value on the document directly. +The following example shows how to set and get the ``first_name`` field for a +``Person`` instance: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start field values default + :end-before: end field values default + +The preceding example first uses the ``first_name`` attribute to set a +value, then calls it again to retrieve the value. + +You can also use the ``[]`` and ``[] =`` methods on a {+odm+} model instance to +access attributes by using hash syntax. The ``[]`` method is an alias +for the ``read_attribute`` method and the ``[] =`` method is an alias +for the ``write_attribute`` method. The following example shows how to +get and set the aliased ``first_name`` field by using the ``[]`` and +``[]=`` methods: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start field values hash + :end-before: end field values hash + +To learn more about these methods, see the following +:ref:`mongoid-read-write-attributes` section of this guide. + +.. _mongoid-read-write-attributes: + +read_attribute and write_attribute +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``read_attribute`` and ``write_attribute`` methods to specify +custom behavior when reading or writing fields. You can use these methods +when defining a model or by calling them on model instances. + +To use ``read_attribute`` to get a field, pass the name of the field to the method. +To use ``write_attribute`` to set a field, pass the name of the field and the +value to assign. + +The following example uses ``read_attribute`` and ``write_attribute`` in +a model definition to define ``first_name`` and ``first_name=`` as methods that +are used to read and write to the ``fn`` attribute: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start read write attributes + :end-before: end read write attributes + +You can also call ``read_attribute`` and ``write_attribute`` directly on a +model instance to get and set attributes. The following example uses these methods +on a model instance to get the ``first_name`` attribute and set it to the value +``"Pushkin"``. + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start read write instance + :end-before: end read write instance + +Bulk Write Attributes +~~~~~~~~~~~~~~~~~~~~~ + +You can write to multiple fields at the same time by using the ``attributes=`` +or ``write_attributes`` methods on a model instance. + +To use the ``attributes=`` method, call the method on a model instance and +pass a hash object that contains the fields and values that you want to set. +The following example shows how to use the ``attributes=`` method to set the +``first_name`` and ``middle_name`` fields on a ``person`` document: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start attributes= example + :end-before: end attributes= example + +To use the ``write_attributes`` method, call the method on a model instance +and pass the fields and values that you want to set. The following example +shows how to use the ``write_attributes`` method to set the ``first_name`` and +``middle_name`` fields on a ``person`` document: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start write_attributes example + :end-before: end write_attributes example + +Atomic Update Operators +----------------------- + +{+odm+} provides support for the following update operators that you can +call as methods on model instances. These methods perform operations +atomically and skip validations and callbacks. + +The following table describe the operators supported by {+odm+}: + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Operator + - Description + - Example + + * - ``add_to_set`` + - Adds a specified value to an array-valued field. + - ``person.add_to_set(aliases: "Bond")`` + + * - ``bit`` + - Performs a bitwise update of a field. + - ``person.bit(age: { and: 10, or: 12 })`` + + * - ``inc`` + - Increments the value of a field. + - ``person.inc(age: 1)`` + + * - ``pop`` + - Removes the first or last element of an array field. + - ``person.pop(aliases: 1)`` + + * - ``pull`` + - Removes all instances of a value or values that match a specified + condition from an array field. + - ``person.pull(aliases: "Bond")`` + + * - ``pull_all`` + - Removes all instances of the specified values from an array + field. + - ``person.pull_all(aliases: [ "Bond", "James" ])`` + + * - ``push`` + - Appends a specified value to an array field. + - ``person.push(aliases: ["007","008"])`` + + * - ``rename`` + - Renames a field in all matching documents. + - ``person.rename(bday: :dob)`` + + * - ``set`` + - | Updates an attribute on the model instance and, if the instance + is already persisted, performs an atomic ``$set`` on the field, bypassing + validations. + + | ``set`` can also deeply set values on ``Hash`` fields. + + | ``set`` can also deeply set values on ``embeds_one`` associations. + If a model instance's ``embeds_one`` association document is ``nil``, one + is created before the update. + + | ``set`` cannot be used with ``has_one`` associations. + + - .. code-block:: ruby + + person = Person.create!(name: "Ricky Bobby") + # Updates `name` in the database + person.set(name: "Tyler Durden") + + * - ``unset`` + - Deletes a particular field in all matching documents. + - ``person.unset(:name)`` + +To learn more about update operators, see :manual:`Update Operators +` in the MongoDB {+server-manual+}. + +Group Atomic Operations +~~~~~~~~~~~~~~~~~~~~~~~ + +To group atomic operations together, you can use the ``atomically`` method +on a model instance. {+odm+} sends all operations that you pass to an +``atomically`` block in a single atomic command. + +.. note:: Use Transactions to Modify Multiple Documents Atomically + + Atomic operations apply to one document at a time. Therefore, nested + ``atomically`` blocks cannot make changes to multiple documents in one + atomic operation. To make changes to multiple documents in one atomic + operation, use a multi-document transaction. To learn more about + transactions, see the :ref:`mongoid-data-txn` guide. + +The following example shows how to use ``atomically`` to atomically +update multiple fields in a document: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start atomically example + :end-before: end atomically example + +You can nest ``#atomically`` blocks when updating a single document. By +default, {+odm+} performs atomic writes defined by each block when the +block ends. The following example shows how to nest ``atomically`` blocks: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start default block atomic example + :end-before: end default block atomic example + +In the preceding example, the ``$inc`` and ``$set`` operations are executed at +the end of the inner ``atomically`` block. + +Join Contexts ++++++++++++++ + +The ``atomically`` method accepts a ``join_context: true`` option to specify that +operations execute at the end of the outermost ``atomically`` block. When you +enable this option, only the outermost block, or the first block where ``join_context`` +is ``false``, writes changes to the database. The following example sets +the ``join_context`` option to ``true``: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start join_contexts atomic + :end-before: end join_contexts atomic + +In the preceding example, {+odm+} performs the ``$inc`` and ``$set`` operations at the +end of the outermost ``atomically`` block. However, since an exception is raised +before the block ends and these operations can run, the changes are not +persisted. + +You can also enable context joining globally, so that operations execute in the +outermost ``atomically`` block by default. To enable this option +globally, set the ``join_contexts`` configuration option to ``true`` in +your ``mongoid.yml`` file. To learn more about {+odm+} configuration +options, see :ref:`configuration-options`. + +When you globally set ``join_contexts`` to ``true``, you can use the +``join_context: false`` option on an ``atomically`` block to run +operations at the end of the block for that block only. + +Additional Information +---------------------- + +To learn more about specifying query filters, see the +:ref:`mongoid-data-specify-query` guide. + +To learn more about setting validation rules on your models, see the +:ref:`mongoid-modeling-validation` guide. + +To learn more about defining callbacks, see the +:ref:`mongoid-modeling-callbacks` guide. From 39e5eeea9d5df3a6c5a1ba7205c17fdb6622b576 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Fri, 10 Jan 2025 14:17:43 -0500 Subject: [PATCH 097/113] DOCSP-45110: queries subsections (#80) * DOCSP-45110: queries misc sections * wip: * vale * MR PR fixes 1 * GM PR fixes 1 --- source/interact-data/query-async.txt | 92 +++++++++++++ source/interact-data/query-cache.txt | 111 ++++++++++++++++ source/interact-data/query-persistence.txt | 142 +++++++++++++++++++++ source/interact-data/specify-query.txt | 11 ++ 4 files changed, 356 insertions(+) create mode 100644 source/interact-data/query-async.txt create mode 100644 source/interact-data/query-cache.txt create mode 100644 source/interact-data/query-persistence.txt diff --git a/source/interact-data/query-async.txt b/source/interact-data/query-async.txt new file mode 100644 index 00000000..166b24b4 --- /dev/null +++ b/source/interact-data/query-async.txt @@ -0,0 +1,92 @@ +.. _mongoid-query-async: + +==================== +Asynchronous Queries +==================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, memory, background tasks, execution + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to perform asynchronous queries in +{+odm+}. You can run database queries asynchronously in the background, +which can be beneficial if your application retrieves documents from +multiple collections. + +Run Async Queries +----------------- + +To schedule an asynchronous query, call the ``load_async`` method on +a ``Criteria`` instance, as shown in the following code: + +.. code-block:: ruby + + @active_bands = Band.where(active: true).load_async + @public_articles = Article.where(public: true).load_async + +The preceding code schedules the queries for asynchronous execution. +You can then access the results of the queries in your view as you +normally do for synchronous queries. + +Even if you schedule a query for asynchronous execution, it might be +executed synchronously on the caller's thread. The following list +describes possible scenarios in which this situation might occur: + +- If {+odm+} completes the scheduled asynchronous task, it returns + the results. + +- If {+odm+} starts but does not complete the task, the caller's + thread blocks until {+odm+} finishes the task. + +- If {+odm+} has not started a task yet, it is removed from the + execution queue, and {+odm+} runs the query synchronously on the + caller's thread. + +.. note:: + + Even though the ``load_async`` method returns a ``Criteria`` object, + do not perform any operations on this object other than accessing query results. + {+odm+} schedules the query for execution immediately after calling + ``load_async``, so later changes to the ``Criteria`` object might not + be applied. + +Configure Query Performance +--------------------------- + +Asynchronous queries are disabled by default. When asynchronous queries +are disabled, the ``load_async`` method performs the query immediately +on the current thread, blocking as required. Therefore, calling +``load_async`` on a ``Criteria`` instance in this situation is similar +to calling the ``to_a`` method to force query execution. + +To enable asynchronous query execution, you must set the following +configuration options: + +.. code-block:: xml + + development: + ... + options: + # Execute asynchronous queries using a global thread pool. + async_query_executor: :global_thread_pool + # Number of threads in the pool. The default is 4. + # global_executor_concurrency: 4 + +Additional Information +---------------------- + +.. TODO link to config guide + +.. TODO link to crud operations \ No newline at end of file diff --git a/source/interact-data/query-cache.txt b/source/interact-data/query-cache.txt new file mode 100644 index 00000000..2b477dee --- /dev/null +++ b/source/interact-data/query-cache.txt @@ -0,0 +1,111 @@ +.. _mongoid-query-cache: + +=========== +Query Cache +=========== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, memory, storage, execution + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn about **query caching**. The +query cache saves the results of previous find and aggregation queries +and reuses them in the future. This prevents {+odm+} from performing +the queries again, increasing application performance and reducing +the database load. + +To learn more about this feature, see :ruby:`Query Cache +` in the {+ruby-driver+} documentation. + +Enable Query Caching +-------------------- + +In this section, you can learn how to enable the query caching feature +in your application. You can enable the query cache by using the +driver's namespace or {+odm+}'s namespace. + +Automatic +~~~~~~~~~ + +The {+ruby-driver+} provides middleware to automatically enable the +query cache for Rack web requests and Active Job job runs. To view +instructions on automatically enabling the query cache, see the +:ref:`Query Cache Rack Middleware ` section of +the configuration guide. + +.. note:: + + Query cache middleware does not apply to code run outside web + requests or jobs. + +Manual +~~~~~~ + +To enable the query cache manually for a specific code segment, you can +run your code within the following block: + +.. code-block:: ruby + + Mongo::QueryCache.cache do + # Include code here ... + end + +You can explicitly enable and disable the query cache, but we recommend +using the block form in the preceding code example. The following code +demonstrates how to enable and disable the query cache: + +.. code-block:: ruby + + begin + Mongo::QueryCache.enabled = true + # Include code here + ensure + Mongo::QueryCache.enabled = false + end + +Cache the Result of the first Method +------------------------------------ + +Calling the ``first`` method on a model class uses an ascending sort on +the ``_id`` field when returning the result. This might produce unexpected +behavior if you enable query caching. + +For example, if you call the ``all`` method on a model class before +calling ``first``, you might expect the ``first`` method to use the +cached results from ``all``. However, because {+odm+} applies a sort +to the second call, both methods query the database and separately cache +results. + +To use the cached results when calling the ``first`` method, call +``all.to_a.first`` on the model class, as shown in the following example +code: + +.. code-block:: + + Band.all.to_a.first + +In the preceding example, chaining the ``to_a`` method runs the query +and converts the results into an array in memory. Then, the ``first`` +method simply returns the first array entry instead of triggering +another query and caching the results. + +Additional Information +---------------------- + +To learn more about creating filter criteria, see the +:ref:`mongoid-data-specify-query` guide. + +To learn how to customize your persistence target, see the +:ref:`mongoid-persistence` guide. diff --git a/source/interact-data/query-persistence.txt b/source/interact-data/query-persistence.txt new file mode 100644 index 00000000..c6a42f77 --- /dev/null +++ b/source/interact-data/query-persistence.txt @@ -0,0 +1,142 @@ +.. _mongoid-query-persistence: + +========================= +Persist Data from Queries +========================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, crud, filter, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how how to persist data off of your queries +in {+odm+}. {+odm+} supports persistence operations off of criteria in a +limited capacity, allowing you to to expressively perform multi-document +insert, update, and delete operations. + +To learn more about creating filter criteria, see the +:ref:`mongoid-data-specify-query` guide. + +.. TODO To learn more about performing CRUD operations, see the :ref:`` guide. + +Persistence Methods +------------------- + +This section describes methods that you can chain to your queries to +create, update, and delete data in your MongoDB collections. + +Create a Document +~~~~~~~~~~~~~~~~~ + +You can use the following methods to create new documents from your +query criteria: + +- ``create``: Saves a model instance to MongoDB + + - Example: ``Band.where(name: 'Daft Punk').create`` + +- ``create!``: Saves a model instance to MongoDB or raises an exception + if a validation error occurs + + - Example: ``Band.where(name: 'Daft Punk').create!`` + +- ``build``: Creates an unsaved model instance + + - Example: ``Band.where(name: 'Daft Punk').build`` + +- ``new``: Creates an unsaved model instance + + - Example: ``Band.where(name: 'Daft Punk').new`` + +Update Documents +~~~~~~~~~~~~~~~~ + +You can use the following methods to update documents based on your +query criteria: + +- ``update``: Updates attributes of the first matching document + + - Example: ``Band.where(name: 'Sundown').update(label: 'ABC Records')`` + +- ``update_all``: Updates attributes of all matching documents + + - Example: ``Band.where(country: 'Canada').update_all(label: 'ABC Records')`` + +- ``add_to_set``: Adds a value to a specified array in all matching documents + + - Example: ``Band.where(name: 'Sun Down').add_to_set(label: 'ABC Records')`` + +- ``bit``: Performs a bitwise update of a field + + - Example: ``Band.where(name: 'Sun Down').bit(likes: { and: 14, or: 4 })`` + +- ``inc``: Increments the value of a field + + - Example: ``Band.where(name: 'Sun Down').inc(likes: 14)`` + +- ``pop``: Removes the first or last element of an array field + + - Example: ``Band.where(name: 'Sun Down').pop(members: -1)`` + +- ``pull``: Removes all instances of a value or values that match a + specified condition from an array field + + - Example: ``Band.where(name: 'Sun Down').pull(members: 'Jonah Larsen')`` + +- ``pull_all``: Removes all instances of the specified values from an array field + + - Example: ``Band.where(name: 'Sun Down').pull_all(:members, [ 'Jonah Larsen', 'Dan Jones' ])`` + +- ``push``: Appends a specified value to an array field + + - Example: ``Band.where(name: 'Sun Down').push(members: 'Jonah Larsen')`` + +- ``push_all``: Appends a specified value by using the ``$each`` + operator in an array field + + - Example: ``Band.where(name: 'Sun Down').push_all(members: [ 'Jonah Larsen', 'Dan Jones' ])`` + +- ``rename``: Renames a field in all matching documents + + - Example: ``Band.where(name: 'Sun Down').rename(name: :title)`` + +- ``set``: Sets a new value for a specified field in all matching + documents + + - Example: ``Band.where(name: 'Sun Down').set(likes: 10000)`` + +- ``unset``: Deletes a particular field in all matching documents + + - Example: ``Band.where(name: 'Sun Down').unset(:likes)`` + +Delete Documents +~~~~~~~~~~~~~~~~ + +You can use the following methods to delete documents based on your +query criteria: + +- ``delete``: Deletes all matching documents. + + - Example: ``Band.where(label: 'ABC Records').delete`` + +- ``destroy``: Deletes all matching documents while + running callbacks. This method loads all documents into memory. + + - Example: ``Band.where(label: 'ABC Records').destroy`` + +Additional Information +---------------------- + +To learn how to customize your persistence target, see the +:ref:`mongoid-persistence` guide. diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 6c706674..6e70e805 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -15,6 +15,9 @@ Specify a Query :caption: Queries /interact-data/scoping + /interact-data/query-persistence + /interact-data/query-cache + /interact-data/query-async .. contents:: On this page :local: @@ -904,3 +907,11 @@ To learn how to modify the way that {+odm+} returns results to you, see To learn more about defining scopes on your models, see :ref:`mongoid-data-scoping`. + +To learn about methods that you can chain to your queries to persist +data, see :ref:`mongoid-query-persistence`. + +To learn about the query cache feature, see :ref:`mongoid-query-cache`. + +To learn about performing asychronous queries, see +:ref:`mongoid-query-async`. From 491c4ce7f6f4ebb202ec9a5d9733a714fc45bd6e Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:28:37 -0800 Subject: [PATCH 098/113] DOCSP-46072 Associations part 2 (#82) --- source/data-modeling/associations.txt | 414 +++++++++++++++++- .../data-modeling/association-behaviors.rb | 228 ++++++++++ 2 files changed, 641 insertions(+), 1 deletion(-) create mode 100644 source/includes/data-modeling/association-behaviors.rb diff --git a/source/data-modeling/associations.txt b/source/data-modeling/associations.txt index f441f598..88d65315 100644 --- a/source/data-modeling/associations.txt +++ b/source/data-modeling/associations.txt @@ -25,7 +25,7 @@ this guide, you can learn about the different types of associations that {+odm+} supports and how to use them in your application. Referenced Associations ------------------------- +----------------------- Referenced associations allow you to create a relationship between two models where one model references the other. {+odm+} supports the following referenced @@ -515,3 +515,415 @@ method to remove all embedded ``Album`` documents from the ``Band`` class: :language: ruby :start-after: # start-embedded-destroy-all :end-before: # end-embedded-destroy-all + +Customize Association Behavior +------------------------------ + +You can use {+odm+} to customize how associations behave in your application. +The following sections describe ways to customize association behaviors. + +Extensions +~~~~~~~~~~ + +Extensions allow you to add custom functionality to an association. You can +define an extension on an association by specifying a block in the association +definition, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-extensions + :end-before: # end-extensions + :emphasize-lines: 4-6 + +Custom Association Names +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use the ``class_name`` macro to specify a custom class name for an +association. This is useful when you want to name the association something other +than the name of the class. The following example uses the +``class_name`` macro to specify that an embedded association called ``records`` +represents the ``Album`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-name + :end-before: # end-custom-name + +Custom Keys +~~~~~~~~~~~ + +By default, {+odm+} uses the ``_id`` field of the parent class when looking up +associations. You can specify different fields to use by using the +``primary_key`` and ``foreign_key`` macros. The following example specifies a new +primary and foreign key for the ``albums`` association on a ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-keys + :end-before: # end-custom-keys + +If you are specifying a ``has_and_belongs_to_many`` association, you can also +use the ``inverse_primary_key`` and ``inverse_foreign_key`` macros. The +``inverse_primary_key`` macro specifies the field on the local model that the +remote model uses to look up the documents. +The ``inverse_foreign_key`` macro specifies the field on the remote model +that stores the values found in ``inverse_primary_key``. + +The following example specifies a new primary and foreign key for the +``Band`` and ``Members`` classes in a ``has_and_belongs_to_many`` association: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-inverse-keys + :end-before: # end-custom-inverse-keys + :emphasize-lines: 9, 20 + +Custom Scopes +~~~~~~~~~~~~~ + +You can specify the scope of an association by using the ``scope`` parameter. +The ``scope`` parameter determines the documents that {+odm+} considers part +of an association. A scoped association returns only documents that match the +scope conditions when queried. You can set the ``scope`` to either a ``Proc`` with an arity +of zero or a ``Symbol`` that references a named scope on the associated model. +The following example sets custom scopes on associations in a ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-scope + :end-before: # end-custom-scope + +.. note:: + + You can add documents that do not match the scope conditions to an + association. {+odm+} saves the documents to the database and they will appear + in associated memory. However, you won't see the documents when querying the + association. + +Validations +~~~~~~~~~~~ + +When {+odm+} loads an association into memory, by default, it uses the +``validates_associated`` macro to validate that any children are also present. +{+odm+} validates children for the following association types: + +- ``embeds_many`` +- ``embeds_one`` +- ``has_many`` +- ``has_one`` +- ``has_and_belongs_to_many`` + +You can turn off this validation behavior by setting the ``validate`` macro to +``false`` when defining the association, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-validation-false + :end-before: # end-validation-false + +Polymorphism +~~~~~~~~~~~~ + +{+odm+} supports polymorphism on the child classes of one-to-one and one-to-many associations. +Polymorphic associations allows a single association to contain objects of different class +types. You can define a polymorphic association by setting the ``polymorphic`` +option to ``true`` in a child association and adding the ``as`` option to the +parent association. The following example creates a polymorphic association in a +``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-polymorphic + :end-before: # end-polymorphic + +In the preceding example, the ``:featured`` association in the ``Band`` class can contain either a +``Label`` or ``Album`` document. + +.. important:: + + {+odm+} supports polymorphism only from child to parent. You cannot specify + a parent ``has_one`` or ``has_many`` association as polymorphic. + +``has_and_belongs_to_many`` associations do not support polymorphism. + +Custom Polymorphic Types +```````````````````````` + +Starting in version 9.0.2, {+odm+} supports custom polymorphic types through +a global registry. You can specify alternative keys to represent different +classes, decoupling your code from the data. The following example specifies +the string ``"artist"`` as an alternate key for the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-custom-polymorphic + :end-before: # end-custom-polymorphic + +In the preceding example, the ``identify_as`` directive instructs {+odm+} +to store the ``Band`` class in the database as the string ``"artist"``. + +You can also specify multiple aliases, as shown in the following example: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-multiple-alias + :end-before: # end-multiple-alias + +In the preceding example, ``artist`` is the default name and the others are used +only for looking up records. This allows you to refactor your +code without breaking the associations in your data. + +Polymorphic type aliases are global. The keys you specify must be unique across your +entire code base. However, you can register alternative resolvers that +can be used for different subsets of your models. In this case, the keys must +be unique only for each resolver. The following example shows how to register +alternate resolvers: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-polymorphic-resolvers + :end-before: # end-polymorphic-resolvers + +Both ``Music::Band`` and ``Tools::Band`` are aliased as +``"bnd"``, but each model uses its own resolver to avoid conflicts. + +Dependent Behavior +~~~~~~~~~~~~~~~~~~ + +You can provide ``dependent`` options to referenced associations to specify how +{+odm+} handles associated documents when a document is deleted. You can specify +the following options: + +- ``delete_all``: Deletes all child documents without running any model + callbacks. +- ``destroy``: Deletes the child documents and runs all model callbacks. +- ``nullify``: Sets the foreign key of the child documents to ``nil``. The child + document might become orphaned if it is referenced by only the parent. +- ``restrict_with_exception``: Raises an exception if the child document is not + empty. +- ``restrict_with_error``: Cancels the operation and returns ``false`` if the + child document is not empty. + +If you don't specify any ``dependent`` options, {+odm+} leaves the child +document unchanged when the parent document is deleted. The child document +continues to reference the deleted parent document, and if it is +referenced through only the parent, the child document becomes orphaned. + +The following example specifies ``dependent`` options on the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-dependent + :end-before: # end-dependent + +Autosave Referenced Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, {+odm+} does not automatically save associated documents from +non-embedded associations when saving the parent document. This can +result in dangling references to documents that don't exist. + +You can use the ``autosave`` option on a referenced association to +automatically save associated documents when saving the parent document. The +following example creates a ``Band`` class with an associated ``Album`` class +and specifies the ``autosave`` option: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-autosave + :end-before: # end-autosave + :emphasize-lines: 10 + +.. note:: + + {+odm+} automatically adds autosave functionality to an association that uses + the ``accepts_nested_attributes_for`` option. + +You do not need to specify the ``autosave`` option for embedded associations +because {+odm+} saves embedded documents in the parent document. + +Autobuild +~~~~~~~~~ + +You can add the ``autobuild`` option to one-to-one associations, such as +``has_one`` and ``embeds_one``, to automatically instantiate a new document when +accessing a ``nil`` association. The following example adds the ``autobuild`` +option to an association on the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-autobuild + :end-before: # end-autobuild + +Touch +~~~~~ + +When {+odm+} *touches* a document, it updates the document's +``updated_at`` field to the current date and time. You can add the ``touch`` +option to any ``belongs_to`` association to ensure that {+odm+} touches the +parent document whenever the child document is updated. The following example +adds the ``touch`` option to an association on the ``Band`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-touch + :end-before: # end-touch + +You can also use the ``touch`` option to specify another field on the parent +association, as a string or a symbol. When {+odm+} touches the parent +association, it sets both the ``updated_at`` field and the specified field +to the current date and time. + +The following example instructs {+odm+} to touch the ``bands_updated_at`` field: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-touch-specific + :end-before: # end-touch-specific + +.. note:: + + In embedded associations, when an embedded document is touched, {+odm+} + touches its parents recursively. Because of this, adding a ``touch`` + attribute to an ``embedded_in`` association is unnecessary. + + {+odm+} does not support specifying additional fields to touch in + ``embedded_in`` associations. + +Counter Cache +~~~~~~~~~~~~~ + +You can use the ``counter_cache`` option to store the number of objects +that belong to an associated field. When you specify this option, {+odm+} stores +an extra attribute on the associated models to store the count. Because of this, +you must specify the ``Mongoid::Attributes::Dynamic`` module in the associated +classes. + +The following example adds the ``counter_cache`` option to a ``Band`` +class and specifies the ``Mongoid::Attributes::Dynamic`` in a ``Label`` class: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-counter-cache + :end-before: # end-counter-cache + :emphasize-lines: 4, 9 + +Association Metadata +-------------------- + +When you define an association, {+odm+} stores metadata about that association. +You can access the metadata by calling the ``reflect_on_association`` method on +a model class or document, or by directly accessing the metadata on a specific +document. The following example shows how to access metadata by using the +``reflect_on_association`` method and by direct access: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-access-metadata + :end-before: # end-access-metadata + +.. note:: + + Replace ```` in the preceding example with the name of your + association. + +Attributes +~~~~~~~~~~ + +All associations contain attributes that store information about the associated +document. Associations contain the following attributes: + +- ``_target``: The proxied document or documents +- ``_base``: The document on which the association is defined +- ``_association``: Information about the association + +The following example accesses each of the preceding attributes: + +.. literalinclude:: /includes/data-modeling/association-behaviors.rb + :language: ruby + :start-after: # start-attributes + :end-before: # end-attributes + +The following table shows the information stored in the ``_association`` +attribute: + +.. list-table:: + :header-rows: 1 + :widths: 30 60 + + * - Method + - Description + * - ``Association#as`` + - The name of the parent to a polymorphic child. + * - ``Association#as?`` + - Returns whether an ``as`` option exists. + * - ``Association#autobuilding?`` + - Returns whether the association is autobuilding. + * - ``Association#autosaving?`` + - Returns whether the association is autosaving. + * - ``Association#cascading_callbacks?`` + - Returns whether the association has callbacks cascaded down from the parent. + * - ``Association#class_name`` + - The class name of the proxied document. + * - ``Association#cyclic?`` + - Returns whether the association is a cyclic association. + * - ``Association#dependent`` + - The association's dependent option. + * - ``Association#destructive?`` + - Returns ``true`` if the association has a dependent delete or destroy method. + * - ``Association#embedded?`` + - Returns whether the association is embedded in another document. + * - ``Association#forced_nil_inverse?`` + - Returns whether the association has a ``nil`` inverse defined. + * - ``Association#foreign_key`` + - The name of the foreign-key field. + * - ``Association#foreign_key_check`` + - The name of the foreign-key field's dirty-check method. + * - ``Association#foreign_key_setter`` + - The name of the foreign-key field's setter. + * - ``Association#indexed?`` + - Returns whether the foreign key is auto indexed. + * - ``Association#inverses`` + - The names of all inverse associations. + * - ``Association#inverse`` + - The name of a single inverse association. + * - ``Association#inverse_class_name`` + - The class name of the association on the inverse side. + * - ``Association#inverse_foreign_key`` + - The name of the foreign-key field on the inverse side. + * - ``Association#inverse_klass`` + - The class of the association on the inverse side. + * - ``Association#inverse_association`` + - The metadata of the association on the inverse side. + * - ``Association#inverse_of`` + - The explicitly defined name of the inverse association. + * - ``Association#inverse_setter`` + - The name of the method used to set the inverse. + * - ``Association#inverse_type`` + - The name of the polymorphic-type field of the inverse. + * - ``Association#inverse_type_setter`` + - The name of the polymorphic-type field's setter of the inverse. + * - ``Association#key`` + - The name of the field in the attribute's hash that is used to get the association. + * - ``Association#klass`` + - The class of the proxied documents in the association. + * - ``Association#name`` + - The association name. + * - ``Association#options`` + - Returns ``self``, for API compatibility with ActiveRecord. + * - ``Association#order`` + - The custom sorting options on the association. + * - ``Association#polymorphic?`` + - Returns whether the association is polymorphic. + * - ``Association#setter`` + - The name of the field to set the association. + * - ``Association#store_as`` + - The name of the attribute in which to store an embedded association. + * - ``Association#touchable?`` + - Returns whether the association has a touch option. + * - ``Association#type`` + - The name of the field to get the polymorphic type. + * - ``Association#type_setter`` + - The name of the field to set the polymorphic type. + * - ``Association#validate?`` + - Returns whether the association has an associated validation. diff --git a/source/includes/data-modeling/association-behaviors.rb b/source/includes/data-modeling/association-behaviors.rb new file mode 100644 index 00000000..a74d0739 --- /dev/null +++ b/source/includes/data-modeling/association-behaviors.rb @@ -0,0 +1,228 @@ +# start-extensions +class Band + include Mongoid::Document + + embeds_many :albums do + def find_by_name(name) + where(name: name).first + end + end +end + +band.albums.find_by_name("Omega") # returns album "Omega" +# end-extensions + +# start-custom-name +class Band + include Mongoid::Document + + embeds_many :records, class_name: "Album" +end +# end-custom-name + +# start-custom-keys +class Band + include Mongoid::Document + + field :band_id, type: String + has_many :albums, primary_key: 'band_id', foreign_key: 'band_id_ref' +end + +class Album + include Mongoid::Document + + field :band_id_ref, type: String + belongs_to :band, primary_key: 'band_id', foreign_key: 'band_id_ref' +end +# end-custom-keys + +# start-custom-inverse-keys +class Band + include Mongoid::Document + + field :band_id, type: String + field :member_ids, type: Array + + has_many :members, + primary_key: 'member_id', foreign_key: 'member_ids', + inverse_primary_key: 'band_id', inverse_foreign_key: 'band_ids' +end + +class Member + include Mongoid::Document + + field :member_id, type: String + field :band_ids, type: Array + + has_many :bands, + primary_key: 'band_id', foreign_key: 'band_ids', + inverse_primary_key: 'member_id', inverse_foreign_key: 'member_ids' +end +# end-custom-inverse-keys + +# start-custom-scope +class Band + include Mongoid::Document + + has_many :albums, scope: -> { where(published: true) } + + # Uses a scope called "upcoming" on the Tour model + has_many :tours, scope: :upcoming +end +# end-custom-scope + +# start-validation-false +class Band + include Mongoid::Document + + embeds_many :albums, validate: false +end +# end-validation-false + +# start-polymorphic +class Tour + include Mongoid::Document + + has_one :band, as: :featured +end + +class Label + include Mongoid::Document + + has_one :band, as: :featured +end + +class Band + include Mongoid::Document + + belongs_to :featured, polymorphic: true +end +# end-polymorphic + +# start-custom-polymorphic +class Band + include Mongoid::Document + + identify_as 'artist' + has_many :albums, as: :record +end +# end-custom-polymorphic + +# start-multiple-alias +class Band + include Mongoid::Document + + identify_as 'artist', 'group', 'troupe' + has_many :albums, as: :record +end +# end-multiple-alias + +# start-polymorphic-resolvers +Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :mus +Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :tool + +module Music + class Band + include Mongoid::Document + + identify_as 'bnd', resolver: :mus + end +end + +module Tools + class Band + include Mongoid::Document + + identify_as 'bnd', resolver: :tool + end +end +# end-polymorphic-resolvers + +# start-dependent +class Band + include Mongoid::Document + + has_many :albums, dependent: :delete_all + belongs_to :label, dependent: :nullify +end +# end-dependent + +# start-autosave +class Band + include Mongoid::Document + + has_many :albums +end + +class Album + include Mongoid::Document + + belongs_to :band, autosave: true +end + +band = Band.new +album = Album.create!(band: band) +# The band is persisted at this point. +# end-autosave + +# start-autobuild +class Band + include Mongoid::Document + + embeds_one :label, autobuild: true + has_one :producer, autobuild: true +end +# end-autobuild + +# start-touch +class Band + include Mongoid::Document + + field :name + belongs_to :label, touch: true +end +# end-touch + +# start-touch-specific +class Band + include Mongoid::Document + + belongs_to :label, touch: :bands_updated_at +end +# end-touch-specific + +# start-counter-cache +class Band + include Mongoid::Document + + belongs_to :label, counter_cache: true +end + +class Label + include Mongoid::Document + include Mongoid::Attributes::Dynamic + + has_many :bands +end +# end-counter-cache + +# start-access-metadata +# Get the metadata for a named association from the class or document +Model.reflect_on_association(:) + +# Directly access metadata on a document +model.associations[:] +# end-access-metadata + +# start-attributes +class Band + include Mongoid::Document + + embeds_many :songs +end + +Band.songs = [ song ] +Band.songs._target # returns [ song ] +Band.songs._base # returns band +Band.songs._association # returns the association metadata +# end-attributes \ No newline at end of file From 78cb9137b8268e287f12a7c6aea29fb8121bb89f Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:38:21 -0500 Subject: [PATCH 099/113] DOCSP-46394: CRUD remaining sections (#83) * DOCSP-46394: CRUD remaining sections * vale fixes * JS PR fixes 1 --- source/includes/interact-data/crud.rb | 72 ++++++++++++- source/interact-data/crud.txt | 148 ++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) diff --git a/source/includes/interact-data/crud.rb b/source/includes/interact-data/crud.rb index 85dbc78b..290a51b1 100644 --- a/source/includes/interact-data/crud.rb +++ b/source/includes/interact-data/crud.rb @@ -263,4 +263,74 @@ class Person raise 'An exception' # Name and age changes are not persisted end -# end join_contexts atomic \ No newline at end of file +# end join_contexts atomic + +# start-dirty-tracking-view +# Retrieves a person instance +person = Person.first +# Sets a new `name` value +person.name = "Sarah Frank" + +# Checks to see if the document is changed +person.changed? # true +# Gets an array of changed fields. +person.changed # [ :name ] +# Gets a hash of the old and changed values for each field +person.changes # { "name" => [ "Sarah Frink", "Sarah Frank" ] } + +# Checks if a specific field is changed +person.name_changed? # true +# Gets the changes for a specific field +person.name_change # [ "Sarah Frink", "Sarah Frank" ] + +# Gets the previous value for a field +person.name_was # "Sarah Frink" +# end-dirty-tracking-view + +# start-dirty-tracking-reset +person = Person.first +person.name = "Sarah Frank" + +# Reset the changed `name` field +person.reset_name! +person.name # "Sarah Frink" +# end-dirty-tracking-reset + +# start-dirty-tracking-prev +person = Person.first +person.name = "Sarah Frank" +person.save # Clears out current changes + +# Lists the previous changes +person.previous_changes +# { "name" => [ "Sarah Frink", "Sarah Frank" ] } +# end-dirty-tracking-prev + +# start-container-save +person = Person.new +interests = person.interests +# => # +interests << 'Hiking' +# => # + +# Assigns the Set to the field +person.interests = interests +# => # +person.interests +# => # +# end-container-save + +# start-override-readonly +class Person + include Mongoid::Document + field :name, type: String + + def readonly? + true + end +end + +person = Person.first +person.readonly? # => true +person.destroy # => raises ReadonlyDocument error +# end-override-readonly diff --git a/source/interact-data/crud.txt b/source/interact-data/crud.txt index 41eede34..26cc04fe 100644 --- a/source/interact-data/crud.txt +++ b/source/interact-data/crud.txt @@ -677,6 +677,154 @@ When you globally set ``join_contexts`` to ``true``, you can use the ``join_context: false`` option on an ``atomically`` block to run operations at the end of the block for that block only. +Dirty Tracking +-------------- + +You can track changed ("dirty") fields by using a {+odm+} API similar to +the one available in Active Model. If you modify a defined field in a +model, {+odm+} marks the model as dirty and allows you to perform +special actions. The following sections describe how you can interact +with dirty models. + +View Changes +~~~~~~~~~~~~ + +{+odm+} records changes from the time a model is instantiated, either as +a new document or by retrieving one from the database, until the time it is +saved. Any persistence operation clears the changes. + +{+odm+} creates model-specific methods that allow you to explore the +changes to a model instance. The following code demonstrates ways that +you can view changes on your model instance: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start-dirty-tracking-view + :end-before: end-dirty-tracking-view + +.. note:: Tracking Changes to Associations + + Setting the associations on a document does not modify the + ``changes`` or ``changed_attributes`` hashes. This is true for all + types of associations. However, changing the + ``_id`` field on referenced associations causes the changes to + show up in the ``changes`` and the ``changed_attributes`` hashes. + +Reset Changes +~~~~~~~~~~~~~ + +You can reset a changed field to its previous value by calling the +``reset`` method, as shown in the following code: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start-dirty-tracking-reset + :end-before: end-dirty-tracking-reset + +Persistence +~~~~~~~~~~~ + +{+odm+} uses dirty tracking as the basis of all persistence operations. +It evaluates the changes on a document and atomically updates only what +has changed, compared to other frameworks that write the entire document on +each save. If you don't make any changes, {+odm+} does not access the +database when you call ``Model#save``. + +View Previous Changes +~~~~~~~~~~~~~~~~~~~~~ + +After you persist a model to MongoDB, {+odm+} clears the current +changes. However, you can still see what changes were made previously by +calling the ``previous_changes`` method, as shown in the following +code: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start-dirty-tracking-prev + :end-before: end-dirty-tracking-prev + +Update Container Fields +----------------------- + +{+odm+} currently has an issue that prevents changes to attributes of +container types, such as ``Set`` or ``Array``, from saving to MongoDB. +You must assign all fields, including container types, for their values +to save to MongoDB. + +For example, adding an item to a ``Set`` instance as shown in the +following code *does not* persist changes to MongoDB: + +.. code-block:: ruby + + person = Person.new + person.interests + # => # + + person.interests << 'Hiking' + # => # + person.interests + # => # # Change does not take effect + +To persist this change, you must modify the field value *outside* of the +model and assign it back to the model as shown in the following code: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start-container-save + :end-before: end-container-save + +Read-only Documents +------------------- + +You can mark documents as read-only in the following ways, depending on +the value of the ``Mongoid.legacy_readonly`` feature flag: + +- If this flag is turned *off*, you can mark a document as + read-only by calling the ``readonly!`` method on that document. The + resulting read-only document raises a ``ReadonlyDocument`` error if + you attempt to perform any persistence operation, including, but not + limited to, saving, updating, deleting, and destroying. Note that + reloading *does not* reset the read-only state. + + .. code-block:: ruby + + person = Person.first + person.readonly? # => false + person.readonly! # Sets the document as read-only + person.readonly? # => true + person.name = "Larissa Shay" # Changes the document + person.save # => raises ReadonlyDocument error + person.reload.readonly? # => true + +- If this flag is turned ``on``, you can mark a document as read-only + after you project that document by using methods such as ``only`` or + ``without``. As a result, you can't delete or destroy the read-only + document because {+odm+} raises a ``ReadonlyDocument`` error, but you can + save and update it. The read-only status *is reset* if you reload the + document. + + .. code-block:: ruby + + person = Person.only(:name).first + person.readonly? # => true + person.destroy # => raises ReadonlyDocument error + person.reload.readonly? # => false + + .. tip:: Projection + + To learn more about projections, see the + :ref:`mongoid-data-projection` section of the Modify Query + Results guide. + +You can also make a document read-only by overriding the ``readonly?`` +method, as shown in the following code: + +.. literalinclude:: /includes/interact-data/crud.rb + :language: ruby + :start-after: start-override-readonly + :end-before: end-override-readonly + :emphasize-lines: 5-7 + Additional Information ---------------------- From 56ec00e03940fd29999ef197d27dd705b34f0974 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:13:26 -0500 Subject: [PATCH 100/113] DOCSP-46213: bump to rails 8 and remove old tuts (#84) --- snooty.toml | 4 +- source/quick-start-rails.txt | 2 +- .../download-and-install.txt | 7 +- source/quick-start-rails/view-data.txt | 2 +- source/quick-start-sinatra.txt | 5 +- source/tutorials.txt | 10 - source/tutorials/documents.txt | 18 - source/tutorials/getting-started-rails6.txt | 551 ------------------ source/tutorials/getting-started-rails7.txt | 471 --------------- source/tutorials/getting-started-sinatra.txt | 220 ------- 10 files changed, 5 insertions(+), 1285 deletions(-) delete mode 100644 source/tutorials/documents.txt delete mode 100644 source/tutorials/getting-started-rails6.txt delete mode 100644 source/tutorials/getting-started-rails7.txt delete mode 100644 source/tutorials/getting-started-sinatra.txt diff --git a/snooty.toml b/snooty.toml index 8ea40b05..22980ad3 100644 --- a/snooty.toml +++ b/snooty.toml @@ -16,12 +16,10 @@ toc_landing_pages = [ ] [constants] -rails-6-version = 6.0 -rails-7-version = 7.1 rails-8-version-docs = "v8.0" odm = "Mongoid" version = "9.0" -full-version = "{+version+}.2" +full-version = "{+version+}.4" ruby-driver = "Ruby driver" language = "Ruby" quickstart-sinatra-app-name = "my-sinatra-app" diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index 60de78da..659820ca 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -20,7 +20,7 @@ Quick Start - Ruby on Rails Overview -------- -This guide shows you how to use {+odm+} in a new **Ruby on Rails 7 (Rails)** +This guide shows you how to use {+odm+} in a new **Ruby on Rails 8 (Rails)** web application, connect to a MongoDB cluster hosted on MongoDB Atlas, and perform read and write operations on the data in your cluster. diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index 9814fdbc..cca1e8af 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -14,7 +14,7 @@ Download and Install Prerequisites ------------- -To create the Quick Start application by using Ruby on Rails 7, you need the +To create the Quick Start application by using Ruby on Rails 8, you need the following software installed in your development environment: - `{+language+}. `__ @@ -24,11 +24,6 @@ following software installed in your development environment: - A terminal app and shell. For MacOS users, use Terminal or a similar app. For Windows users, use PowerShell. -.. tip:: Rails 6 Tutorial - - If you prefer to use Rails 6 to build your application, see the - :ref:`mongoid-getting-started-rails-6` guide. - Download and Install the {+odm+} and Framework Gems --------------------------------------------------- diff --git a/source/quick-start-rails/view-data.txt b/source/quick-start-rails/view-data.txt index 57ad2ef2..81f1355c 100644 --- a/source/quick-start-rails/view-data.txt +++ b/source/quick-start-rails/view-data.txt @@ -61,7 +61,7 @@ View MongoDB Data :copyable: false => Booting Puma - => Rails 7.2.1 application starting in development + => Rails 8.0.1 application starting in development => Run `bin/rails server --help` for more startup options Puma starting in single mode... * Puma version: 6.4.3 (ruby 3.2.5-p208) ("The Eagle of Durango") diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index e0e053fd..db7a785d 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -49,11 +49,8 @@ Follow the steps in this guide to create a sample {+odm+} web application that connects to a MongoDB deployment. .. tip:: Other Framework Tutorials - - If you prefer to use Ruby on Rails 6 to build your application, see the - :ref:`mongoid-getting-started-rails-6` guide. - If you prefer to use Ruby on Rails 7 as your web framework, see the + If you prefer to use Ruby on Rails 8 as your web framework, see the :ref:`mongoid-quick-start-rails` guide. .. TODO .. tip:: diff --git a/source/tutorials.txt b/source/tutorials.txt index 5c03cb90..8b30c6e0 100644 --- a/source/tutorials.txt +++ b/source/tutorials.txt @@ -4,15 +4,9 @@ Tutorials ********* -.. default-domain:: mongodb - .. toctree:: :titlesonly: - tutorials/getting-started-sinatra - tutorials/getting-started-rails7 - tutorials/getting-started-rails6 - tutorials/documents tutorials/common-errors tutorials/automatic-encryption @@ -21,9 +15,5 @@ Overview See the following sections to learn more about working with Mongoid: -- :ref:`Getting Started (Sinatra) ` -- :ref:`Getting Started (Rails 7) ` -- :ref:`Getting Started (Rails 6) ` -- :ref:`Documents ` - :ref:`Common Errors ` - :ref:`Automatic Client-Side Field Level Encryption ` \ No newline at end of file diff --git a/source/tutorials/documents.txt b/source/tutorials/documents.txt deleted file mode 100644 index 2bd87f30..00000000 --- a/source/tutorials/documents.txt +++ /dev/null @@ -1,18 +0,0 @@ -.. _documents: - -********* -Documents -********* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Documents are the core objects in Mongoid and any object that is to be persisted to the -database must include ``Mongoid::Document``. The representation of a Document in MongoDB -is a BSON object that is very similar to a Ruby hash or JSON object. Documents can be stored -in their own collections in the database, or can be embedded in other Documents n levels deep. diff --git a/source/tutorials/getting-started-rails6.txt b/source/tutorials/getting-started-rails6.txt deleted file mode 100644 index 0b5e32bd..00000000 --- a/source/tutorials/getting-started-rails6.txt +++ /dev/null @@ -1,551 +0,0 @@ -.. _mongoid-getting-started-rails-6: - -========================= -Getting Started (Rails 6) -========================= - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -In this guide, you can learn how to implement {+odm} in a Ruby on Rails -6 web application. View the following sections to learn how to integrate -{+odm+} in new applications or how to add it to existing applications. - -.. note:: - - This tutorial is for Ruby on Rails 6. To use Ruby on Rails 7 as your - web framework, see the :ref:`mongoid-quick-start-rails` guide. - -New Application ---------------- - -This section shows how to create a new Ruby on Rails application using Mongoid -for data access. The application will be similar to the blog application -described in the `Ruby on Rails Getting Started -`__ -guide, however using Mongoid instead of ActiveRecord as the database adapter. - -The complete source code for this application can be found in the -`mongoid-demo GitHub repository. -`__ - -.. note:: - - This guide assumes basic familiarity with Ruby on Rails. - To learn more about Ruby on Rails, please refer to its `Getting Started - guide `__ or - other Rails guides. - -Install ``rails`` -~~~~~~~~~~~~~~~~~ - -We will use a Rails generator to create the application skeleton. -In order to do so, the first step is to install the ``rails`` gem: - -.. code-block:: sh - - gem install rails -v '~> {+rails-6-version+}' - - -Create New Application -~~~~~~~~~~~~~~~~~~~~~~ - -Use the ``rails`` command to create the application skeleton, as follows: - -.. code-block:: sh - - rails new blog --skip-active-record --skip-bundle - cd blog - -.. note:: - - You may receive a warning like this: - - .. code-block:: sh - - Could not find gem 'puma (~> 3.11)' in any of the gem sources listed in your Gemfile. - Run `bundle install` to install missing gems. - - Disregard it as we will be taking care of gem installation - in a moment. - -We pass ``--skip-active-record`` to request that ActiveRecord is not added -as a dependency, because we will be using Mongoid instead. Additionally -we pass ``--skip-bundle`` because we'll be modifying the ``Gemfile`` to -add the ``mongoid`` dependency. - -If you intend to test your application with RSpec, you can instruct the -generator to omit default Rails test setup by passing ``--skip-test`` -and ``--skip-system-test`` options: - -.. code-block:: sh - - rails new blog --skip-bundle --skip-active-record --skip-test --skip-system-test - cd blog - -Create Git Repo -~~~~~~~~~~~~~~~ - -While not required, we recommend creating a Git repository for your application: - -.. code-block:: sh - - git init . - git add . - git commit - -Commit your changes as you are following this tutorial. - -Add Mongoid -~~~~~~~~~~~ - -1. Modify the ``Gemfile`` to add a reference to the -`mongoid `_ gem: - -.. code-block:: ruby - :caption: Gemfile - - gem 'mongoid' - -.. note:: - - Mongoid 7.0.5 or higher is required to use Rails 6.0. - -2. Install gem dependencies: - -.. code-block:: sh - - bundle install - -3. Generate the default Mongoid configuration: - -.. code-block:: sh - - bin/rails g mongoid:config - -This generator will create the ``config/mongoid.yml`` configuration file -(used to configure the connection to the MongoDB deployment) and the -``config/initializers/mongoid.rb`` initializer file (which may be used for -other Mongoid-related configuration). Note that as we are not using -ActiveRecord we will not have a ``database.yml`` file. - -.. _run-locally: - -Run MongoDB Locally -~~~~~~~~~~~~~~~~~~~ - -The configuration created in the previous step is suitable when -a MongoDB server is running locally. If you do not already have a -local MongoDB server, `download and install MongoDB -`_. - -While the generated ``mongoid.yml`` will work without modifications, -we recommend reducing the server selection timeout for development. -With this change, the uncommented lines of ``mongoid.yml`` should look -like this: - -.. code-block:: none - - development: - clients: - default: - database: blog_development - hosts: - - localhost:27017 - options: - server_selection_timeout: 1 - - -.. _use-atlas: - -Use MongoDB Atlas -~~~~~~~~~~~~~~~~~ - -Instead of downloading, installing and running MongoDB locally, you can create -a free MongoDB Atlas account and create a `free MongoDB cluster in Atlas -`_. -Once the cluster is created, follow the instructions in `connect to the cluster -page `_ -to obtain the URI. Use the *Ruby driver 2.5 or later* format. - -Paste the URI into the ``config/mongoid.yml`` file, and comment out the -hosts that are defined. We recommend setting the server selection timeout to 5 -seconds for development environment when using Atlas. - -The uncommented contents of ``config/mongoid.yml`` should look like this: - -.. code-block:: yaml - - development: - clients: - default: - uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority - options: - server_selection_timeout: 5 - -Other Rails Dependencies -~~~~~~~~~~~~~~~~~~~~~~~~ - -If this is the first Rails application you are creating, you may need to -install Node.js on your computer. This can be done via your operating system -packages or by `downloading a binary `_. - -Next, if you do not have Yarn installed, `follow its installation instructions -`_. - -Finally, install webpacker: - -.. code-block:: sh - - rails webpacker:install - - -Run Application -~~~~~~~~~~~~~~~ - -You can now start the application server by running: - -.. code-block:: sh - - rails s - -Access the application by navigating to `localhost:3000 -`_. - -Add Posts -~~~~~~~~~ - -Using the standard Rails scaffolding, Mongoid can generate the necessary -model, controller and view files for our blog so that we can quickly begin -creating blog posts: - -.. code-block:: sh - - bin/rails g scaffold Post title:string body:text - -Navigate to `localhost:3000/posts `_ -to create posts and see the posts that have already been created. - -.. figure:: ../img/rails-new-blog.png - :alt: Screenshot of the new blog - -Add Comments -~~~~~~~~~~~~ - -To make our application more interactive, let's add the ability for users to -add comments to our posts. - -Create the ``Comment`` model: - -.. code-block:: sh - - bin/rails g scaffold Comment name:string message:string post:belongs_to - -Open the ``Post`` model file, ``app/models/post.rb``, and add a ``has_many`` -association for the comments: - -.. code-block:: ruby - :caption: app/models/post.rb - - class Post - include Mongoid::Document - - field :title, type: String - field :body, type: String - - has_many :comments, dependent: :destroy - end - -.. note:: - *The following is only required if using a version of Mongoid < 7.0.8 or 7.1.2 (see* - `MONGOID-4885 `_ *for details)* - - Open the ``Comment`` model file, ``app/models/comment.rb``, and change the - generated ``embedded_in`` association to ``belongs_to``: - - .. code-block:: ruby - :caption: app/models/comment.rb - - class Comment - include Mongoid::Document - - field :name, type: String - field :message, type: String - - belongs_to :post - end - -Open the post show view file, ``app/views/posts/show.html.erb``, and add -a section rendering existing comments and prompting to leave a new comment: - -.. code-block:: html - :caption: app/views/posts/show.html.erb - -

-
-

- <%= @post.comments.count %> Comments -

- <%= render @post.comments %> -
-
-

Leave a reply

- <%= render partial: 'comments/form', locals: { comment: @post.comments.build } %> -
-
-
- -Open the comment form file and change the type of field for ``:message`` -from ``text_field`` to ``text_area``, as well as the type of field for -``:post_id`` from ``text_field`` to ``hidden_field``. The result -should look like this: - -.. code-block:: html - :caption: app/views/comments/_form.html.erb - - <%= form_with(model: comment, local: true) do |form| %> - <% if comment.errors.any? %> -
-

<%= pluralize(comment.errors.count, "error") %> prohibited this comment from being saved:

- -
    - <% comment.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - -
- <%= form.label :name %> - <%= form.text_field :name %> -
- -
- <%= form.label :message %> - <%= form.text_area :message %> -
- -
- <%= form.hidden_field :post_id %> -
- -
- <%= form.submit %> -
- <% end %> - -Create a partial for the comment view, ``app/views/comments/_comment.html.erb`` -with the following contents: - -.. code-block:: html - :caption: app/views/comments/_comment.html.erb - -

- <%= comment.name %>: - <%= comment.message %> -
- <%= link_to 'Delete', [comment], - method: :delete, - class: "button is-danger", - data: { confirm: 'Are you sure?' } %> -

- -You should now be able to leave comments for the posts: - -.. image:: ../img/rails-blog-new-comment.png - :alt: Screenshot of the blog with a new comment being added - -Existing Application --------------------- - -Follow these steps to switch an existing Ruby on Rails application to use -Mongoid instead of ActiveRecord. - -Dependencies -~~~~~~~~~~~~ - -Remove or comment out any RDBMS libraries like ``sqlite``, ``pg`` etc. -mentioned in ``Gemfile``, and add ``mongoid``: - -.. code-block:: ruby - :caption: Gemfile - - gem 'mongoid' - -.. note:: - - Mongoid 7.0.5 or higher is required to use Rails 6.0. - -Install gem dependencies: - -.. code-block:: sh - - bundle install - -Loaded Frameworks -~~~~~~~~~~~~~~~~~ - -Examine ``config/application.rb``. If it is requiring all components of Rails -via ``require 'rails/all'``, change it to require individual frameworks: - -.. code-block:: ruby - :caption: config/application.rb - - # Remove or comment out - #require "rails/all" - - # Add this require instead of "rails/all": - require "rails" - - # Pick the frameworks you want: - require "active_model/railtie" - require "active_job/railtie" - require "action_controller/railtie" - require "action_mailer/railtie" - # require "action_mailbox/engine" - # require "action_text/engine" - require "action_view/railtie" - require "action_cable/engine" - require "sprockets/railtie" - require "rails/test_unit/railtie" - - # Remove or comment out ActiveRecord and ActiveStorage: - # require "active_record/railtie" - # require "active_storage/engine" - -.. note:: - - At this time ActiveStorage requires ActiveRecord and is not usable with - Mongoid. - -ActiveRecord Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Review all configuration files (``config/application.rb``, -``config/environments/{development,production.test}.rb``) and remove or -comment out any references to ``config.active_record`` and -``config.active_storage``. - -Stop Spring -~~~~~~~~~~~ - -If your application is using Spring, which is the default on Rails 6, -Spring must be stopped after changing dependencies or configuration. - -.. code-block:: sh - - ./bin/spring stop - -.. note:: - - Sometimes running ``./bin/spring stop`` claims to stop Spring, but does - not. Verify that all Spring processes are terminated before proceeding. - -.. note:: - - Sometimes Spring tries to load ActiveRecord even when the application - contains no ActiveRecord references. If this happens, add an ActiveRecord - adapter dependency such as ``sqlite3`` to your ``Gemfile`` so that - ActiveRecord may be completely loaded or remove Spring from your - application. - -Mongoid Configuration -~~~~~~~~~~~~~~~~~~~~~ - -Generate the default Mongoid configuration: - -.. code-block:: sh - - bin/rails g mongoid:config - -This generator will create the ``config/mongoid.yml`` configuration file -(used to configure the connection to the MongoDB deployment) and the -``config/initializers/mongoid.rb`` initializer file (which may be used for -other Mongoid-related configuration). In general, it is recommended to use -``mongoid.yml`` for all Mongoid configuration. - -Review the sections :ref:`Run MongoDB Locally ` and -:ref:`Use MongoDB Atlas ` to decide how you would like to deploy -MongoDB, and adjust Mongoid configuration (``config/mongoid.yml``) to match. - -Adjust Models -~~~~~~~~~~~~~ - -If your application already has models, these will need to be changed when -migrating from ActiveRecord to Mongoid. - -ActiveRecord models derive from ``ApplicationRecord`` and do not have -column definitions. Mongoid models generally have no superclass but must -include ``Mongoid::Document``, and usually define the fields explicitly -(but :ref:`dynamic fields ` may also be used instead of -explicit field definitions). - -For example, a bare-bones Post model may look like this in ActiveRecord: - -.. code-block:: ruby - :caption: app/models/post.rb - - class Post < ApplicationRecord - has_many :comments, dependent: :destroy - end - -The same model may look like this in Mongoid: - -.. code-block:: ruby - :caption: app/models/post.rb - - class Post - include Mongoid::Document - - field :title, type: String - field :body, type: String - - has_many :comments, dependent: :destroy - end - -Or like this with dynamic fields: - -.. code-block:: ruby - :caption: app/models/post.rb - - class Post - include Mongoid::Document - include Mongoid::Attributes::Dynamic - - has_many :comments, dependent: :destroy - end - -Mongoid does not utilize ActiveRecord migrations, since MongoDB does not -require a schema to be defined prior to storing data. - -Data Migration -~~~~~~~~~~~~~~ - -If you already have data in a relational database that you would like to -transfer to MongoDB, you will need to perform a data migration. As noted -above, no schema migration is necessary because MongoDB does not require -a predefined schema to store the data. - -The migration tools are often specific to the data being migrated because, -even though Mongoid supports a superset of ActiveRecord associations, -the way that model references are stored in collections differs between -Mongoid and ActiveRecord. With that said, MongoDB has -some resources on migrating from an RDBMS to MongoDB such as the -`RDBMS to MongoDB Migration Guide `_ and -`Modernization Guide `_. - -Rails API -~~~~~~~~~ - -The process for creating a Rails API application with Mongoid is the same -as when creating a regular application, with the only change being the -``--api`` parameter to ``rails new``. Migrating a Rails API application to -Mongoid follows the same process described above for regular Rails applications. - -A complete Rails API application similar to the one described in this tutorial -can be found in `the mongoid-demo GitHub repository -`_. diff --git a/source/tutorials/getting-started-rails7.txt b/source/tutorials/getting-started-rails7.txt deleted file mode 100644 index 9e7c282d..00000000 --- a/source/tutorials/getting-started-rails7.txt +++ /dev/null @@ -1,471 +0,0 @@ -.. _getting-started-7: - -************************* -Getting Started (Rails 7) -************************* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -.. note:: - - This tutorial is for Ruby on Rails 7. If this is not the version - you're using, choose the appropriate tutorial for your Rails version - from the navigation menu. - -New Application -=============== - -This section demonstrates how to create a new Ruby on Rails application using the Mongoid ODM. -By replacing Rails' default `ActiveRecord `_ -adapter with MongoDB's ORM-like library for data access we will create an application similar to the -blog application described in the `Ruby on Rails Getting Started -`_ guide. - -The complete source code for this application can be found in the -`mongoid-demo GitHub repository -`_. - -.. note:: - - This guide assumes basic familiarity with Ruby on Rails. - To learn more about Ruby on Rails, please refer to its `Getting Started - guide `_ or - other Rails guides. - - -Install ``rails`` ------------------ - -We will use a Rails generator to create the application skeleton. -In order to do so, the first step is to install the ``rails`` gem: - -.. code-block:: sh - - gem install rails -v {+rails-7-version+} - - -Create New Application ----------------------- - -Use the ``rails`` command to create the application skeleton, as follows: - -.. code-block:: sh - - rails new blog --skip-active-record - cd blog - -We pass ``--skip-active-record`` to request that ActiveRecord is not added -as a dependency, because we will be using Mongoid instead. - -Optionally Skip Tests -````````````````````` - -If you intend to test your application with `RSpec `_, you can instruct the -generator to omit the default Rails test setup by passing ``--skip-test`` -and ``--skip-system-test`` options: - -.. code-block:: sh - - rails new blog --skip-active-record --skip-test --skip-system-test - cd blog - -Setup Mongoid -------------- - -1. Modify the ``Gemfile`` to add a reference to the -`mongoid `_ gem: - -.. code-block:: ruby - :caption: Gemfile - - gem 'mongoid' - -2. Install gem dependencies: - -.. code-block:: sh - - bundle install - -3. Generate the default `Mongoid configuration `_: - -.. code-block:: sh - - bin/rails g mongoid:config - -This generator will create the ``config/mongoid.yml`` configuration file -(used to configure the connection to the MongoDB deployment) and the -``config/initializers/mongoid.rb`` initializer file (which may be used for -other Mongoid-related configuration). Note that as we are not using -ActiveRecord we will not have a ``database.yml`` file. - - -.. _configure-self-managed: - -Configure for Self Managed MongoDB -`````````````````````````````````` - -The configuration created in the previous step is suitable when -a MongoDB server is running locally. If you do not already have a -local MongoDB server, `download and install MongoDB -`_. - -While the generated ``mongoid.yml`` will work without modifications, -we recommend reducing the server selection timeout for development. -With this change, the uncommented lines of ``mongoid.yml`` should look -like this: - -.. code-block:: yaml - - development: - clients: - default: - database: blog_development - hosts: - - localhost:27017 - options: - server_selection_timeout: 1 - - -.. _configure-atlas: - -Configure for MongoDB Atlas -``````````````````````````` - -Instead of downloading, installing and running MongoDB locally, you can create -a free MongoDB Atlas account and create a `free MongoDB cluster in Atlas -`_. -Once the cluster is created, follow the instructions in `connect to the cluster -page `_ -to obtain the URI. Use the *Ruby driver 2.5 or later* format. - -Paste the URI into the ``config/mongoid.yml`` file, and comment out the -hosts that are defined. We recommend setting the server selection timeout to 5 -seconds for development environment when using Atlas. - -The uncommented contents of ``config/mongoid.yml`` should look like this: - -.. code-block:: yaml - - development: - clients: - default: - uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority - options: - server_selection_timeout: 5 - -Run Application ---------------- - -You can now start the application server by running: - -.. code-block:: sh - - bin/rails s - -Access the application by navigating to `localhost:3000 -`_. - - -Add Posts ---------- - -Using the standard Rails scaffolding, Mongoid can generate the necessary -model, controller and view files for our blog so that we can quickly begin -creating blog posts: - -.. code-block:: sh - - bin/rails g scaffold Post title:string body:text - -Navigate to `localhost:3000/posts `_ -to create posts and see the posts that have already been created. - -.. figure:: /img/rails-new-blog.png - :alt: Screenshot of the new blog - - -Add Comments ------------- - -To make our application more interactive, let's add the ability for users to -add comments to our posts. - -Create the ``Comment`` model: - -.. code-block:: sh - - bin/rails g scaffold Comment name:string message:string post:belongs_to - -Open the ``Post`` model file, ``app/models/post.rb``, and add a ``has_many`` -association for the comments: - -.. code-block:: ruby - :caption: app/models/post.rb - - class Post - include Mongoid::Document - include Mongoid::Timestamps - field :title, type: String - field :body, type: String - - has_many :comments, dependent: :destroy - end - -Open ``app/views/posts/show.html.erb`` and add -a section rendering existing comments and prompting to leave a new comment: - -.. code-block:: erb - :caption: app/views/posts/show.html.erb - -
-
-

- <%= @post.comments.count %> Comments -

- <%= render @post.comments %> -
-
-

Leave a reply

- <%= render partial: 'comments/form', locals: { comment: @post.comments.build } %> -
-
-
- -Open ``app/views/comments/_form.html.erb`` and change the type of field for ``:message`` -from ``text_field`` to ``text_area``, as well as the type of field for -``:post_id`` from ``text_field`` to ``hidden_field``. The result -should look like this: - -.. code-block:: erb - :caption: app/views/comments/_form.html.erb - - <%= form_with(model: comment, local: true) do |form| %> - <% if comment.errors.any? %> -
-

<%= pluralize(comment.errors.count, "error") %> prohibited this comment from being saved:

- -
    - <% comment.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
- <% end %> - -
- <%= form.label :name %> - <%= form.text_field :name %> -
- -
- <%= form.label :message %> - <%= form.text_area :message %> -
- -
- <%= form.hidden_field :post_id %> -
- -
- <%= form.submit %> -
- <% end %> - -Next replace ``app/view/comments/_comment.html.erb`` with the following contents: - -.. code-block:: erb - :caption: app/views/comments/_comment.html.erb - -

- <%= comment.name %>: - <%= comment.message %> - <%= link_to 'Delete', [comment], - data: { - "turbo-method": :delete, - "turbo-confirm": 'Are you sure?' - } %> -

- - -You should now be able to leave comments for the posts: - -.. figure:: /img/rails-blog-new-comment.png - :alt: Screenshot of the blog with a new comment being added - - -Existing Application -==================== - -Mongoid can be easily added to an existing Rails application and run alongside other ActiveRecord -adapters. If this is your use case, updating dependencies and populating the configuration file will -allow you to start using MongoDB within your application. - -To switch an existing Ruby on Rails application to use Mongoid instead of ActiveRecord additional -configuration changes will be required, as described below. - -Dependencies ------------- - -First, the ``mongoid`` gem will need to be added your ``Gemfile``. - -.. code-block:: ruby - :caption: Gemfile - - gem 'mongoid' - -If Mongoid will be the **only** database adapter, remove or comment out any RDBMS libraries -like ``sqlite`` or ``pg`` mentioned in the ``Gemfile``. - -Install gem dependencies: - -.. code-block:: sh - - bundle install - -Mongoid Configuration ---------------------- - -Generate the default Mongoid configuration: - -.. code-block:: sh - - bin/rails g mongoid:config - -This generator will create the ``config/mongoid.yml`` configuration file -(used to configure the connection to the MongoDB deployment) and the -``config/initializers/mongoid.rb`` initializer file (which may be used for -other Mongoid-related configuration). In general, it is recommended to use -``mongoid.yml`` for all Mongoid configuration. - -Review the sections :ref:`Configure for Self Managed MongoDB ` -and :ref:`Configure for MongoDB Atlas ` to decide how you -would like to deploy MongoDB, and adjust the Mongoid configuration -(``config/mongoid.yml``) to match. - -Loaded Frameworks ------------------ - -Examine ``config/application.rb``. If it is requiring all components of Rails -via ``require 'rails/all'``, change it to require individual frameworks. To verify the contents of -``rails/all`` for your version see the `Github Repository -`_: - -.. code-block:: ruby - :caption: config/application.rb - - # Remove or comment out - #require "rails/all" - - # Add the following instead of "rails/all": - require "rails" - - # require "active_record/railtie" rescue LoadError - # require "active_storage/engine" rescue LoadError - require "action_controller/railtie" rescue LoadError - require "action_view/railtie" rescue LoadError - require "action_mailer/railtie" rescue LoadError - require "active_job/railtie" rescue LoadError - require "action_cable/engine" rescue LoadError - # require "action_mailbox/engine" rescue LoadError - # require "action_text/engine" rescue LoadError - require "rails/test_unit/railtie" rescue LoadError - -.. warning:: - - Due to their reliance on ActiveRecord, `ActionText `_, - `ActiveStorage `_ and - `ActionMailbox `_ cannot be used - with Mongoid. - -ActiveRecord Configuration --------------------------- - -Review all configuration files (``config/application.rb``, -``config/environments/{development,production.test}.rb``) and remove or -comment out any references to ``config.active_record`` and -``config.active_storage``. - -Adjust Models -------------- - -If your application already has models, these will need to be changed when -migrating from ActiveRecord to Mongoid. - -ActiveRecord models derive from ``ApplicationRecord`` and do not have -column definitions. Mongoid models generally have no superclass but must -include ``Mongoid::Document``, and usually define the fields explicitly -(but :ref:`dynamic fields ` may also be used instead of -explicit field definitions). - -For example, a bare-bones Post model may look like this in ActiveRecord: - -.. code-block:: ruby - :caption: app/models/post.rb - - class Post < ApplicationRecord - has_many :comments, dependent: :destroy - end - -The same model may look like this in Mongoid: - -.. code-block:: ruby - :caption: app/models/post.rb - - class Post - include Mongoid::Document - - field :title, type: String - field :body, type: String - - has_many :comments, dependent: :destroy - end - -Or like this with dynamic fields: - -.. code-block:: ruby - :caption: app/models/post.rb - - class Post - include Mongoid::Document - include Mongoid::Attributes::Dynamic - - has_many :comments, dependent: :destroy - end - -Mongoid does not utilize ActiveRecord migrations, since MongoDB does not -require a schema to be defined prior to storing data. - -Data Migration --------------- - -If you already have data in a relational database that you would like to -transfer to MongoDB, you will need to perform a data migration. As noted -above, no schema migration is necessary because MongoDB does not require -a predefined schema to store the data. - -The migration tools are often specific to the data being migrated because, -even though Mongoid supports a superset of ActiveRecord associations, -the way that model references are stored in collections differs between -Mongoid and ActiveRecord. With that said, MongoDB has -some resources on migrating from an RDBMS to MongoDB such as the -`RDBMS to MongoDB Migration Guide `_ and -`Modernization Guide `_. - - -Rails API ---------- - -The process for creating a Rails API application with Mongoid is the same -as when creating a regular application, with the only change being the -``--api`` parameter to ``rails new``. Migrating a Rails API application to -Mongoid follows the same process described above for regular Rails applications. - -A complete Rails API application similar to the one described in this tutorial -can be found in `the mongoid-demo GitHub repository -`_. diff --git a/source/tutorials/getting-started-sinatra.txt b/source/tutorials/getting-started-sinatra.txt deleted file mode 100644 index 832dd0d6..00000000 --- a/source/tutorials/getting-started-sinatra.txt +++ /dev/null @@ -1,220 +0,0 @@ -.. _getting-started-sinatra: - -************************* -Getting Started (Sinatra) -************************* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -New Application -=============== - -This section shows how to create a new Sinatra application using Mongoid -for data access. The process is similar for other Ruby applications not -using Ruby on Rails. - -The complete source code for this application is available `in the -mongoid-demo GitHub repository -`_. - -Create Git Repo ---------------- - -While not required, we recommend creating a Git repository for your application: - -.. code-block:: sh - - git init blog - cd blog - -Commit your changes as you are following this tutorial. - - -Create Gemfile --------------- - -Create a file named ``Gemfile`` with the following contents: - -.. code-block:: ruby - - source 'https://rubygems.org' - - gem 'sinatra' - gem 'mongoid' - gem 'puma' - - -Install Dependencies --------------------- - -Run the following commands to install the dependencies: - -.. code-block:: sh - - gem install bundler - bundle install - -This command will generate a file named ``Gemfile.lock`` which we recommend -committing to your Git repository. - - -Run MongoDB Locally -------------------- - -To develop locally with MongoDB, `download and install MongoDB -`_. - -Once MongoDB is installed and running, create a file named ``config/mongoid.yml`` -pointing to your deployment. For example, if you launched a standalone -``mongod`` on the default port, the following contents would be appropriate: - -.. code-block:: none - - development: - clients: - default: - database: blog_development - hosts: - - localhost:27017 - options: - server_selection_timeout: 1 - - -Use MongoDB Atlas ------------------ - -Instead of downloading, installing and running MongoDB locally, you can create -a free MongoDB Atlas account and create a `free MongoDB cluster in Atlas -`_. -Once the cluster is created, follow the instructions in `connect to the cluster -page `_ -to obtain the URI. Use the *Ruby driver 2.5 or later* format. - -Create a file named ``config/mongoid.yml`` with the following -contents, replacing the URI with the actual URI for your cluster: - -.. code-block:: yaml - - development: - clients: - default: - uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority - options: - server_selection_timeout: 5 - - - -Basic Application ------------------ - -Create a file named ``app.rb`` with the following contents. First, some -requires: - -.. code-block:: ruby - - require 'sinatra' - require 'mongoid' - -Load the Mongoid configuration file and configure Mongoid. This is done -automatically when Mongoid is used with Rails, but since we are using Mongoid -with Sinatra, we need to do this ourselves: - -.. code-block:: ruby - - Mongoid.load!(File.join(File.dirname(__FILE__), 'config', 'mongoid.yml')) - -Now we can define some models: - -.. code-block:: ruby - - class Post - include Mongoid::Document - - field :title, type: String - field :body, type: String - - has_many :comments - end - - class Comment - include Mongoid::Document - - field :name, type: String - field :message, type: String - - belongs_to :post - end - -... and add some routes: - -.. code-block:: ruby - - get '/posts' do - Post.all.to_json - end - - post '/posts' do - post = Post.create!(params[:post]) - post.to_json - end - - get '/posts/:post_id' do |post_id| - post = Post.find(post_id) - post.attributes.merge( - comments: post.comments, - ).to_json - end - - post '/posts/:post_id/comments' do |post_id| - post = Post.find(post_id) - comment = post.comments.create!(params[:comment]) - {}.to_json - end - - -Run Application -=============== - -Launch the application: - -.. code-block:: sh - - bundle exec ruby app.rb - -Try some requests via curl: - -.. code-block:: sh - - curl http://localhost:4567/posts - # => [] - - curl -d 'post[title]=hello&post[body]=hello+world' http://localhost:4567/posts - # => {"_id":{"$oid":"5d8151ec96fb4f0ed5a7a03f"},"body":"hello world","title":"hello"} - - curl http://localhost:4567/posts - # => [{"_id":{"_id":{"$oid":"5d8151ec96fb4f0ed5a7a03f"},"body":"hello world","title":"hello"}] - - curl -d 'comment[name]=David&comment[message]=I+like' http://localhost:4567/posts/5d8151ec96fb4f0ed5a7a03f/comments - # => {} - - curl http://localhost:4567/posts/5d8151ec96fb4f0ed5a7a03f - # => {"_id":{"$oid":"5d8151ec96fb4f0ed5a7a03f"},"title":"hello","body":"hello world","comments":[{"_id":{"$oid":"5d8157ac96fb4f20c5e45c4d"},"message":"I like","name":"David","post_id":{"$oid":"5d8151ec96fb4f0ed5a7a03f"}}]} - - -Existing Application -==================== - -To start using Mongoid in an existing Sinatra applications, the steps are -essentially the same as the one given above for a new application: - -1. Add the ``mongoid`` dependency to the ``Gemfile``. -2. Create a ``mongoid.yml`` configuration file. -3. Load the configuration file and configure Mongoid in the application. -4. Define Mongoid models. From 6d4263f7519f33d693c2e27aa2f2f5eccdfc68c9 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:30:14 -0500 Subject: [PATCH 101/113] DOCSP-45356: i&h + code doc (#86) * DOCSP-45356: i&h + code doc * remove contributing * vale fixes * link fix * vale fixes + RM comment --- snooty.toml | 1 + source/code-documentation.txt | 352 +++++++++++++++++ source/contributing.txt | 21 -- source/contributing/code-documentation.txt | 353 ------------------ .../contributing/contributing-guidelines.txt | 43 --- source/index.txt | 7 +- source/issues-and-help.txt | 85 +++++ 7 files changed, 440 insertions(+), 422 deletions(-) create mode 100644 source/code-documentation.txt delete mode 100644 source/contributing.txt delete mode 100644 source/contributing/code-documentation.txt delete mode 100644 source/contributing/contributing-guidelines.txt create mode 100644 source/issues-and-help.txt diff --git a/snooty.toml b/snooty.toml index 22980ad3..b9bbb429 100644 --- a/snooty.toml +++ b/snooty.toml @@ -13,6 +13,7 @@ toc_landing_pages = [ "/interact-data/specify-query", "/data-modeling", "/configuration", + "/issues-and-help" ] [constants] diff --git a/source/code-documentation.txt b/source/code-documentation.txt new file mode 100644 index 00000000..dd18d384 --- /dev/null +++ b/source/code-documentation.txt @@ -0,0 +1,352 @@ +.. _mongoid-code-documentation: + +================== +Code Documentation +================== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn about {+odm+}'s code documentation +conventions. If you have a suggestion to improve the API documentation +or introduce a new feature, you can use this guide to help you format +your documentation. + +{+odm+} uses a variation of :github:`YARD `, a {+language+} +documentation tool, for its code documentation. + +.. _mongoid-code-documentation-structure: + +Structure +--------- + +- **Modules:** All class and module definitions must be preceded by + a documentation comment. + + .. code-block:: ruby + + # This is the documentation for the class. It's amazing + # what they do with corrugated cardboard these days. + class CardboardBox + +- **Methods:** All method definitions must be preceded by a + documentation comment. Use ``@param``, ``@yield``, and ``@return`` to + specify inputs and output. To learn more, see the + :ref:`mongoid-code-documentation-type-declaration` section. + + .. code-block:: ruby + + # Turn a person into whatever they'd like to be. + # + # @param [ Person ] person The human to transmogrify. + # + # @return [ Tiger ] The transmogrified result. + def transmogrify(person) + +- **Errors:** Use ``@raise`` to describe errors specific to the method. + + .. code-block:: ruby + + # @raise [ Errors::Validations ] If validation failed. + def validate! + +- **Private Methods:** Private methods must be documented unless they are + so brief and straightforward that it is obvious what they do. Note that, + for example, a method might be brief and straightforward but the type of + its parameter may not be obvious, in which case the parameter must be + appropriately documented. + + .. code-block:: ruby + + private + + # Documentation is optional here. + def do_something_obvious + +- **API Private:** Classes and public methods which are not intended for + external usage must be marked ``@api private``. This macro does not + require a comment. + + Note that, because {+odm+}'s modules are mixed into application classes, + ``private`` visibility of a method does not necessarily indicate its + status as an API private method. + + .. code-block:: ruby + + # This is an internal-only method. + # + # @api private + def dont_call_me_from_outside + +- **Notes and TODOs:** Use ``@note`` to explain caveats, edge cases, + and behavior which may surprise users. Use ``@todo`` to record + follow-ups and suggestions for future improvement. + + .. code-block:: ruby + + # Clear all stored data. + # + # @note This operation deletes data in the database. + # @todo Refactor this method for performance. + def erase_data! + +- **Deprecation:** Use the ``@deprecated`` macro to indicate deprecated + functionality. This macro does not require a comment. + + .. code-block:: ruby + + # This is how we did things back in the day. + # + # @deprecated + def the_old_way + +.. _mongoid-code-documentation-formatting: + +Formatting +---------- + +- **Line Wrapping:** Use double-space indent when wrapping lines of macros. + Do not indent line wraps in the description. + + .. code-block:: ruby + + # This is the description of the method. Line wraps in the description + # must not be indented. + # + # @return [ Symbol ] For macros, wraps must be double-space indented + # on the second, third (and so on) lines. + +- **Whitespace:** Do not use leading/trailing empty comment lines, + or more than one consecutive empty comment line. + + .. code-block:: ruby + + # DO THIS: + # @return [ Symbol ] The return value + def my_method + + # NOT THIS: + # @return [ Symbol ] The return value + # + def my_method + + # NOT THIS: + # @param [ Symbol ] foo The input value + # + # + # @return [ Symbol ] The return value + def my_method(foo) + +.. _mongoid-code-documentation-type-declaration: + +Type Declaration +---------------- + +- **Type Unions:** Use pipe ``|`` to denote a union of allowed types. + + .. code-block:: ruby + + # @param [ Symbol | String ] name Either a Symbol or a String. + +- **Nested Types:** Use angle brackets ``< >`` to denote type nesting. + + .. code-block:: ruby + + # @param [ Array ] array An Array of symbols. + +- **Hash:** Use comma ``,`` to denote the key and value types. + + .. code-block:: ruby + + # @param [ Hash ] hash A Hash whose keys are Symbols, + # and whose values are Integers. + +- **Array:** Use pipe ``|`` to denote a union of allowed types. + + .. code-block:: ruby + + # @param [ Array ] array An Array whose members must + # be either Symbols or Strings. + +- **Array:** Use comma ``,`` to denote the types of each position in a tuple. + + .. code-block:: ruby + + # @return [ Array ] A 3-member Array whose first + # element is a Symbol, and whose second and third elements are Integers. + +- **Array:** Use pipe ``|`` on the top level if the inner types cannot be + mixed within the Array. + + .. code-block:: ruby + + # @param [ Array | Array ] array An Array containing only + # Symbols, or an Array containing only Hashes. The Array may not contain + # a mix of Symbols and Hashes. + +- **Nested Types:** For clarity, use square brackets ``[ ]`` to denote nested unions + when commas are also used. + + .. code-block:: ruby + + # @param [ Hash ] hash A Hash whose keys are Symbols, + # and whose values are boolean values. + +- **Ruby Values:** Specific values may be denoted in the type using Ruby syntax. + + .. code-block:: ruby + + # @param [ :before | :after ] timing One of the Symbol values :before or :after. + +- **True, False, and Nil:** Use ``true``, ``false``, and ``nil`` rather than + ``TrueClass``, ``FalseClass``, and ``NilClass``. Do not use ``Boolean`` as a type + since it does not exist in Ruby. + + .. code-block:: ruby + + # DO THIS: + # @param [ true | false | nil ] bool A boolean or nil value. + + # NOT THIS: + # @param [ TrueClass | FalseClass | NilClass ] bool A boolean or nil value. + # @param [ Boolean ] bool A boolean value. + +- **Return Self:** Specify return value ``self`` where a method returns ``self``. + + .. code-block:: ruby + + # @return [ self ] Returns the object itself. + +- **Splat Args:** Use three-dot ellipses ``...`` in the type declaration and + star ``*`` in the parameter name to denote a splat. + + .. code-block:: ruby + + # @param [ String... ] *items The list of items' names as Strings. + def buy_groceries(*items) + +- **Splat Args:** Do not use ``Array`` as the type unless each arg is actually an Array. + + .. code-block:: ruby + + # DO NOT DO THIS: + # @param [ Array ] *items The list of items' names as Strings. + def buy_groceries(*items) + + buy_groceries("Cheese", "Crackers", "Wine") + + # DO THIS: + # @param [ Array... ] *arrays One or more arrays containing name parts. + def set_people_names(*arrays) + + set_people_names(["Harlan", "Sanders"], ["Jane", "K", ""Doe"], ["Jim", "Beam"]) + +- **Splat Args:** Use comma ``,`` to denote positionality in a splat. + + .. code-block:: ruby + + # @param [ Symbol..., Hash ] *args A list of names, followed by a hash + # as the optional last arg. + def say_hello(*args) + +- **Splat Args:** Specify type unions with square brackets ``[ ]``. + + .. code-block:: ruby + + # @param [ [ String | Symbol ]... ] *fields A splat of mixed Symbols and Strings. + +- **Keyword Arguments:** Following YARD conventions, use ``@param`` for keyword + arguments, and specify keyword argument names as symbols. + + .. code-block:: ruby + + # @param [ String ] query The search string + # @param [ Boolean ] :exact_match Whether to do an exact match + # @param [ Integer ] :results_per_page Number of results + def search(query, exact_match: false, results_per_page: 10) + +- **Hash Options:** Define hash key-value options with ``@option`` macro + immediately following the Hash ``@param``. Note ``@option`` parameter names + are symbols. + + .. code-block:: ruby + + # @param opts [ Hash ] The optional hash argument or arguments. + # @option opts [ String | Array ] :items The items as Strings to include. + # @option opts [ Integer ] :limit An Integer denoting the limit. + def buy_groceries(opts = {}) + +- **Double Splats:** Use double-star ``**`` in the parameter name to denote a + keyword arg splat (double splat). Note that type does not need declared on + the double-splat element, as it is implicitly . Instead, + define value types with ``@option`` macro below. Note ``@option`` parameter + names are symbols. + + .. code-block:: ruby + + # @param **kwargs The optional keyword argument or arguments. + # @option **kwargs [ String | Array ] :items The items as Strings to include. + # @option **kwargs [ Integer ] :limit An Integer denoting the limit. + def buy_groceries(**kwargs) + +- **Blocks:** Use ``@yield`` to specify when the method yields to a block. + + .. code-block:: ruby + + # @yield [ Symbol, Symbol, Symbol ] Evaluate the guess of who did the crime. + # Must take the person, location, and weapon used. Must return true or false. + def whodunit + yield(:mustard, :ballroom, :candlestick) + end + +- **Blocks:** If the method explicitly specifies a block argument, specify the block + argument using ``@param`` preceded by an ampersand ``&``, and also specify ``@yield``. + Note ``@yield`` must be used even when method calls ``block.call`` rather than + ``yield`` internally. + + .. code-block:: ruby + + # @param &block The block. + # @yield [ Symbol, Symbol, Symbol ] Evaluate the guess of who did the crime. + # Must take the person, location, and weapon used. Must return true or false. + def whodunit(&block) + yield(:scarlet, :library, :rope) + end + + # @param &block The block. + # @yield [ Symbol, Symbol, Symbol ] Evaluate the guess of who did the crime. + # Must take the person, location, and weapon used. Must return true or false. + def whodunit(&block) + block.call(:plum, :kitchen, :pipe) + end + +- **Blocks:** Use ``@yieldparam`` and ``@yieldreturn`` instead of ``@yield`` where + beneficial for clarity. + + .. code-block:: ruby + + # @param &block The block. + # @yieldparam [ Symbol ] The person. + # @yieldparam [ Symbol ] The location. + # @yieldparam [ Symbol ] The weapon used. + # @yieldreturn [ true | false ] Whether the guess is correct. + def whodunit(&block) + yield(:peacock, :conservatory, :wrench) + end + +- **Proc Args:** Use ``@param`` (not ``@yield``) for proc arguments. The + inputs to the proc may be specified as subtypes. + + .. code-block:: ruby + + # @param [ Proc ] my_proc Proc argument which must + # take 3 integers and must return true or false whether the guess is valid. + def guess_three(my_proc) + my_proc.call(42, 7, 88) + end diff --git a/source/contributing.txt b/source/contributing.txt deleted file mode 100644 index abb7d737..00000000 --- a/source/contributing.txt +++ /dev/null @@ -1,21 +0,0 @@ -.. _contributing: - -************ -Contributing -************ - -.. default-domain:: mongodb - -.. toctree:: - :titlesonly: - - contributing/code-documentation - contributing/contributing-guidelines - -Overview --------- - -Learn how to contribute to the Mongoid repository in the following sections: - -- :ref:`Code Documentation ` -- :ref:`Contributing Guidelines ` \ No newline at end of file diff --git a/source/contributing/code-documentation.txt b/source/contributing/code-documentation.txt deleted file mode 100644 index 9713ce27..00000000 --- a/source/contributing/code-documentation.txt +++ /dev/null @@ -1,353 +0,0 @@ -.. _code-documentation: - -****************** -Code Documentation -****************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -Code Documentation -================== - -Mongoid uses its own flavor of `YARD `_ -for code documentation. Please note the conventions outlined in this document. - - -.. _code-documentation-structure: - -Structure ---------- - -- **Modules:** All class and module definitions should be preceded by - a documentation comment. - - .. code-block:: ruby - - # This is the documentation for the class. It's amazing - # what they do with corrugated cardboard these days. - class CardboardBox - -- **Methods:** All method definitions should be preceded by a documentation comment. - Use ``@param``, ``@yield``, and ``@return`` to specify inputs and output. - For further details, refer to - :ref:`Type Declaration ` below. - - .. code-block:: ruby - - # Turn a person into whatever they'd like to be. - # - # @param [ Person ] person The human to transmogrify. - # - # @return [ Tiger ] The transmogrified result. - def transmogrify(person) - -- **Errors:** Use ``@raise`` to explain errors specific to the method. - - .. code-block:: ruby - - # @raise [ Errors::Validations ] If validation failed. - def validate! - -- **Private Methods:** Private methods should be documented unless they are - so brief and straightforward that it is obvious what they do. Note that, - for example, a method may be brief and straightforward but the type of - its parameter may not be obvious, in which case the parameter must be - appropriately documented. - - .. code-block:: ruby - - private - - # Documentation is optional here. - def do_something_obvious - -- **API Private:** Classes and public methods which are not intended for - external usage should be marked ``@api private``. This macro does not - require a comment. - - Note that, because Mongoid's modules are mixed into application classes, - ``private`` visibility of a method does not necessarily indicate its - status as an API private method. - - .. code-block:: ruby - - # This is an internal-only method. - # - # @api private - def dont_call_me_from_outside - -- **Notes and TODOs:** Use ``@note`` to explain caveats, edge cases, - and behavior which may surprise users. Use ``@todo`` to record - follow-ups and suggestions for future improvement. - - .. code-block:: ruby - - # Clear all stored data. - # - # @note This operation deletes data in the database. - # @todo Refactor this method for performance. - def erase_data! - -- **Deprecation:** Use the ``@deprecated`` macro to indicate deprecated - functionality. This macro does not require a comment. - - .. code-block:: ruby - - # This is how we did things back in the day. - # - # @deprecated - def the_old_way - - -.. _code-documentation-formatting: - -Formatting ----------- - -- **Line Wrapping:** Use double-space indent when wrapping lines of macros. - Do not indent line wraps in the description. - - .. code-block:: ruby - - # This is the description of the method. Line wraps in the description - # should not be indented. - # - # @return [ Symbol ] For macros, wraps must be double-space indented - # on the second, third, etc. lines. - -- **Whitespace:** Do not use leading/trailing empty comment lines, - or more than one consecutive empty comment line. - - .. code-block:: ruby - - # GOOD: - # @return [ Symbol ] The return value - def my_method - - # BAD: - # @return [ Symbol ] The return value - # - def my_method - - # BAD: - # @param [ Symbol ] foo The input value - # - # - # @return [ Symbol ] The return value - def my_method(foo) - - -.. _code-documentation-type-declaration: - -Type Declaration ----------------- - -- **Type Unions:** Use pipe ``|`` to denote a union of allowed types. - - .. code-block:: ruby - - # @param [ Symbol | String ] name Either a Symbol or a String. - -- **Nested Types:** Use angle brackets ``< >`` to denote type nesting. - - .. code-block:: ruby - - # @param [ Array ] array An Array of symbols. - -- **Hash:** Use comma ``,`` to denote the key and value types. - - .. code-block:: ruby - - # @param [ Hash ] hash A Hash whose keys are Symbols, - # and whose values are Integers. - -- **Array:** Use pipe ``|`` to denote a union of allowed types. - - .. code-block:: ruby - - # @param [ Array ] array An Array whose members must - # be either Symbols or Strings. - -- **Array:** Use comma ``,`` to denote the types of each position in a tuple. - - .. code-block:: ruby - - # @return [ Array ] A 3-member Array whose first - # element is a Symbol, and whose second and third elements are Integers. - -- **Array:** Use pipe ``|`` on the top level if the inner types cannot be - mixed within the Array. - - .. code-block:: ruby - - # @param [ Array | Array ] array An Array containing only - # Symbols, or an Array containing only Hashes. The Array may not contain - # a mix of Symbols and Hashes. - -- **Nested Types:** For clarity, use square brackets ``[ ]`` to denote nested unions - when commas are also used. - - .. code-block:: ruby - - # @param [ Hash ] hash A Hash whose keys are Symbols, - # and whose values are boolean values. - -- **Ruby Values:** Specific values may be denoted in the type using Ruby syntax. - - .. code-block:: ruby - - # @param [ :before | :after ] timing One of the Symbol values :before or :after. - -- **True, False, and Nil:** Use ``true``, ``false``, and ``nil`` rather than - ``TrueClass``, ``FalseClass``, and ``NilClass``. Do not use ``Boolean`` as a type - since it does not exist in Ruby. - - .. code-block:: ruby - - # GOOD: - # @param [ true | false | nil ] bool A boolean or nil value. - - # BAD: - # @param [ TrueClass | FalseClass | NilClass ] bool A boolean or nil value. - # @param [ Boolean ] bool A boolean value. - -- **Return Self:** Specify return value ``self`` where a method returns ``self``. - - .. code-block:: ruby - - # @return [ self ] Returns the object itself. - -- **Splat Args:** Use three-dot ellipses ``...`` in the type declaration and - star ``*`` in the parameter name to denote a splat. - - .. code-block:: ruby - - # @param [ String... ] *items The list of items name(s) as Strings. - def buy_groceries(*items) - -- **Splat Args:** Do not use ``Array`` as the type unless each arg is actually an Array. - - .. code-block:: ruby - - # BAD: - # @param [ Array ] *items The list of items name(s) as Strings. - def buy_groceries(*items) - - buy_groceries("Cheese", "Crackers", "Wine") - - # OK: - # @param [ Array... ] *arrays One or more arrays containing name parts. - def set_people_names(*arrays) - - set_people_names(["Harlan", "Sanders"], ["Jane", "K", ""Doe"], ["Jim", "Beam"]) - -- **Splat Args:** Use comma ``,`` to denote positionality in a splat. - - .. code-block:: ruby - - # @param [ Symbol..., Hash ] *args A list of names, followed by a hash - # as the optional last arg. - def say_hello(*args) - -- **Splat Args:** Specify type unions with square brackets ``[ ]``. - - .. code-block:: ruby - - # @param [ [ String | Symbol ]... ] *fields A splat of mixed Symbols and Strings. - -- **Keyword Arguments:** Following YARD conventions, use ``@param`` for keyword - arguments, and specify keyword argument names as symbols. - - .. code-block:: ruby - - # @param [ String ] query The search string - # @param [ Boolean ] :exact_match Whether to do an exact match - # @param [ Integer ] :results_per_page Number of results - def search(query, exact_match: false, results_per_page: 10) - -- **Hash Options:** Define hash key-value options with ``@option`` macro - immediately following the Hash ``@param``. Note ``@option`` parameter names - are symbols. - - .. code-block:: ruby - - # @param opts [ Hash ] The optional hash argument(s). - # @option opts [ String | Array ] :items The items(s) as Strings to include. - # @option opts [ Integer ] :limit An Integer denoting the limit. - def buy_groceries(opts = {}) - -- **Double Splats:** Use double-star ``**`` in the parameter name to denote a - keyword arg splat (double splat). Note that type does not need declared on - the double-splat element, as it is implicitly . Instead, - define value types with ``@option`` macro below. Note ``@option`` parameter - names are symbols. - - .. code-block:: ruby - - # @param **kwargs The optional keyword argument(s). - # @option **kwargs [ String | Array ] :items The items(s) as Strings to include. - # @option **kwargs [ Integer ] :limit An Integer denoting the limit. - def buy_groceries(**kwargs) - -- **Blocks:** Use ``@yield`` to specify when the method yields to a block. - - .. code-block:: ruby - - # @yield [ Symbol, Symbol, Symbol ] Evaluate the guess of who did the crime. - # Must take the person, location, and weapon used. Must return true or false. - def whodunit - yield(:mustard, :ballroom, :candlestick) - end - -- **Blocks:** If the method explicitly specifies a block argument, specify the block - argument using ``@param`` preceded by an ampersand ``&``, and also specify ``@yield``. - Note ``@yield`` should be used even when method calls ``block.call`` rather than - ``yield`` internally. - - .. code-block:: ruby - - # @param &block The block. - # @yield [ Symbol, Symbol, Symbol ] Evaluate the guess of who did the crime. - # Must take the person, location, and weapon used. Must return true or false. - def whodunit(&block) - yield(:scarlet, :library, :rope) - end - - # @param &block The block. - # @yield [ Symbol, Symbol, Symbol ] Evaluate the guess of who did the crime. - # Must take the person, location, and weapon used. Must return true or false. - def whodunit(&block) - block.call(:plum, :kitchen, :pipe) - end - -- **Blocks:** Use ``@yieldparam`` and ``@yieldreturn`` instead of ``@yield`` where - beneficial for clarity. - - .. code-block:: ruby - - # @param &block The block. - # @yieldparam [ Symbol ] The person. - # @yieldparam [ Symbol ] The location. - # @yieldparam [ Symbol ] The weapon used. - # @yieldreturn [ true | false ] Whether the guess is correct. - def whodunit(&block) - yield(:peacock, :conservatory, :wrench) - end - -- **Proc Args:** Proc arguments should use ``@param`` (not ``@yield``). The - inputs to the proc may be specified as subtype(s). - - .. code-block:: ruby - - # @param [ Proc ] my_proc Proc argument which must - # take 3 integers and must return true or false whether the guess is valid. - def guess_three(my_proc) - my_proc.call(42, 7, 88) - end diff --git a/source/contributing/contributing-guidelines.txt b/source/contributing/contributing-guidelines.txt deleted file mode 100644 index 68491bd5..00000000 --- a/source/contributing/contributing-guidelines.txt +++ /dev/null @@ -1,43 +0,0 @@ -.. _contributing-guidelines: - -*********************** -Contributing Guidelines -*********************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -Contributing Guidelines -======================= - -If you wish to propose an enhancement to Mongoid, please create a Jira ticket -describing the enhancement and what it would enable you to achieve in your -application that is not already possible. If you believe Mongoid is not -behaving correctly, please create a Jira ticket describing how you use Mongoid, -what the existing behavior is that you consider incorrect or problematic, and -what your desired behavior is. If you wish to make changes yourself, the -following guildelines should be followed: - -#. Create a fork of Mongoid. -#. Create a new branch in that fork. -#. Make your changes. -#. Ensure that the proposed changes have adequate test coverage. -#. Raise a PR against Mongoid master. If these changes correspond to a specific - Jira ticket, title the PR: "MONGOID- Description of Changes". -#. The Mongoid team will review the PR and make comments/suggest changes. -#. Once all of the changes and fixes are made, and the Mongoid team determine - the PR fit for merging, we will merge the PR into master and determine - whether it needs to be backported. -#. Backports to previous stable versions are done if the change is a bug fix, - is not backwards breaking, and if the commit is applicable to the - corresponding stable branch. Presently backport candidates would include - versions 7.3-8.0. -#. Changes to 6.0-7.2 are generally not made unless it fixes a security - vulnerability. -#. 5.x and earlier is generally not supported. \ No newline at end of file diff --git a/source/index.txt b/source/index.txt index 75728bb0..0bb10fa9 100644 --- a/source/index.txt +++ b/source/index.txt @@ -18,12 +18,9 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Interact with Data Model Your Data Configuration - .. installation-configuration - .. tutorials - .. schema-configuration - working-with-data + /working-with-data API /release-notes - /contributing + Issues & Help /additional-resources /ecosystem diff --git a/source/issues-and-help.txt b/source/issues-and-help.txt new file mode 100644 index 00000000..7b19a3af --- /dev/null +++ b/source/issues-and-help.txt @@ -0,0 +1,85 @@ +.. _mongoid-issues-and-help: + +============= +Issues & Help +============= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: mongoid, ruby, troubleshooting, feedback + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +.. toctree:: + :titlesonly: + + Code Documentation + +We are lucky to have a vibrant MongoDB {+language+} community that includes users +with varying levels of experience using {+odm+} and the {+ruby-driver+}. +The quickest way to get support for general questions is through the +:community-forum:`MongoDB Community Forums `. + +Bugs / Feature Requests +----------------------- + +If you have feedback about {+odm+}, visit the `MongoDB +Feedback Engine `__ and select +:guilabel:`Drivers` from the list of products on the right side of +your screen. You can propose improvements, report issues, and provide +other types of feedback by using this site. + +You can also open a case in Jira, our issue management tool, to identify +bugs or propose improvements. The following steps describe how to create +a Jira issue: + +1. Visit the `MongoDB Jira issue tracker `__ and click the + `signup link. `__ + Create an account, and then log in to Jira. +#. Navigate to the `MONGOID Jira project. `__ +#. Click :guilabel:`Create` to create a ticket. Please provide as much + information as possible about the issue or request in the ticket. + +.. note:: + + Bug reports in the MONGOID Jira project are publicly viewable. + +If you’ve identified a security vulnerability in any official MongoDB +product, please report it according to the instructions found in the +:manual:`Create a Vulnerability Report +` guide. + +Pull Requests +------------- + +We are happy to accept contributions to help improve the driver. We will guide +user contributions to ensure they meet the standards of the codebase. Please +ensure that any pull requests include documentation, tests, and pass +code checks. To learn about {+odm+}'s code documentation conventions, +see the :ref:`mongoid-code-documentation` guide. + +To get started, clone the source repository and work on a branch by +running the following commands: + +.. code-block:: bash + + git clone https://github.com/mongodb/mongoid.git + cd mongoid + git checkout -b my-new-feature + +.. tip:: + + If your changes correspond to a specific Jira ticket, title your pull + request by using the following convention: + + ``MONGOID-: `` + +The {+odm+} developer team will review your pull request and make +comments or suggest changes. From be81b94c19b1fe1b68c3a10947b03ce71f9b2904 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:07:12 -0500 Subject: [PATCH 102/113] DOCSP-42770: release notes/whats new (#87) * DOCSP-42770: release notes/whats new * fixes * fixes --- source/index.txt | 2 +- source/legacy-files/readme | 1 + .../upgrading.txt | 0 source/release-notes/mongoid-7.3.txt | 310 ------- source/release-notes/mongoid-7.4.txt | 584 ------------ source/release-notes/mongoid-7.5.txt | 273 ------ source/release-notes/mongoid-8.0.txt | 866 ------------------ source/release-notes/mongoid-8.1.txt | 431 --------- source/release-notes/mongoid-9.0.txt | 598 ------------ source/whats-new.txt | 567 ++++++++++++ 10 files changed, 569 insertions(+), 3063 deletions(-) create mode 100644 source/legacy-files/readme rename source/{release-notes => legacy-files}/upgrading.txt (100%) delete mode 100644 source/release-notes/mongoid-7.3.txt delete mode 100644 source/release-notes/mongoid-7.4.txt delete mode 100644 source/release-notes/mongoid-7.5.txt delete mode 100644 source/release-notes/mongoid-8.0.txt delete mode 100644 source/release-notes/mongoid-8.1.txt delete mode 100644 source/release-notes/mongoid-9.0.txt create mode 100644 source/whats-new.txt diff --git a/source/index.txt b/source/index.txt index 0bb10fa9..4077ab12 100644 --- a/source/index.txt +++ b/source/index.txt @@ -20,7 +20,7 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Configuration /working-with-data API - /release-notes + /whats-new Issues & Help /additional-resources /ecosystem diff --git a/source/legacy-files/readme b/source/legacy-files/readme new file mode 100644 index 00000000..20941ba0 --- /dev/null +++ b/source/legacy-files/readme @@ -0,0 +1 @@ +This folder contains pages that we haven't standardized yet. \ No newline at end of file diff --git a/source/release-notes/upgrading.txt b/source/legacy-files/upgrading.txt similarity index 100% rename from source/release-notes/upgrading.txt rename to source/legacy-files/upgrading.txt diff --git a/source/release-notes/mongoid-7.3.txt b/source/release-notes/mongoid-7.3.txt deleted file mode 100644 index 6e6f5868..00000000 --- a/source/release-notes/mongoid-7.3.txt +++ /dev/null @@ -1,310 +0,0 @@ -.. _mongoid-7.3: - -*********** -Mongoid 7.3 -*********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -This page describes significant changes and improvements in Mongoid 7.3. -The complete list of releases is available `on GitHub -`_ and `in JIRA -`_; -please consult GitHub releases for detailed release notes and JIRA for -the complete list of issues fixed in each release, including bug fixes. - - -``delete`` Method Does Not Trigger Association Dependent Behavior ----------------------------------------------------------------------- - -**Breaking change:** In Mongoid 7.3, -:ref:`dependent behavior ` is not invoked -when the parent association is deleted using the ``delete`` method. -For example, after the following code snippet executes, in Mongoid 7.3 the -album will remain in the database: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_many :albums, dependent: :destroy - end - - class Album - include Mongoid::Document - - belongs_to :band - end - - band = Band.create! - album = Album.create!(band: band) - - # Does not delete the album from the database - band.delete - -Previous versions of Mongoid invoked dependent behavior when deleting parents. - -To invoke dependent behavior, use the ``destroy`` method: - -.. code-block:: ruby - - # Deletes the album from the database - band.destroy - -The behavior of Mongoid 7.3 is consistent with how ActiveRecord behaves. - - -``::Boolean`` Removed ---------------------- - -**Breaking change:** Mongoid 7.3 removes the global ``::Boolean`` class. - -This change should have no impact on classes that simply use ``Boolean`` -fields, as the ``Boolean`` class is aliased from ``Mongoid::Fields`` -(which is included in ``Mongoid::Document``). The following field definition -continues to work in 7.3 as it did in 7.2: - -.. code-block:: ruby - - class User - include Mongoid::Document - - field :verified, type: Boolean - end - -However, code that is not executed in the context of a class including -``Mongoid::Document`` may need to explicitly qualify ``Boolean`` references. -The following snippet fails with Mongoid 7.3 due to ``Boolean`` being -unqualified: - -.. code-block:: ruby - - class User - include Mongoid::Document - end - - User.field :verified, type: Boolean - -To fix it, use the fully-qualified ``Mongoid::Boolean`` class: - -.. code-block:: ruby - - User.field :verified, type: Mongoid::Boolean - -Note that ``class_eval`` is executed in the scope of the caller, not in -the scope of the class being modified. Thus even when using ``class_eval`` -it is necessary to fully qualify ``Mongoid::Boolean``: - -.. code-block:: ruby - - User.class_eval do - field :verified, type: Mongoid::Boolean - end - -Additionally, in Mongoid 7.2 ``::Boolean`` and ``Mongoid::Boolean`` were -different classes. In Mongoid 7.3 there is only one class which is -``Mongoid::Boolean``. - -It is possible to restore the global ``::Boolean`` class by executing in -your application: - -.. code-block:: ruby - - Boolean = Mongoid::Boolean - -Note that this aliases ``Mongoid::Boolean`` to ``::Boolean`` such that there -is still only a single Boolean class: - -.. code-block:: ruby - - # With Mongoid 7.3: - Boolean = Mongoid::Boolean - Boolean == Mongoid::Boolean - # => true - - # With Mongoid 7.2: - Boolean == Mongoid::Boolean - # => false - - -Selector Key Stringification ----------------------------- - -Minor change: Mongoid now converts symbol keys to string keys in the -``Criteria`` selectors. This applies to operators as well as hash literals. - -Mongoid 7.3 behavior: - -.. code-block:: ruby - - Band.and(year: {'$in': [2020]}) - # => - # #{"$in"=>[2020]}} - # options: {} - # class: Band - # embedded: false> - - Band.where(tag: {city: 1}) - # => - # #{"city"=>1}} - # options: {} - # class: Band - # embedded: false> - -Mongoid 7.2 behavior: - -.. code-block:: ruby - - Band.and(year: {'$in': [2020]}) - # => - # #{:$in=>[2020]}} - # options: {} - # class: Band - # embedded: false> - - Band.where(tag: {city: 1}) - # => - # #{:city=>1}} - # options: {} - # class: Band - # embedded: false> - - -Condition Combination Using ``$eq`` / ``$regex`` ------------------------------------------------- - -Minor change: when using the ``where``, ``and``, ``or``, and ``nor`` methods -on ``Criteria`` objects and providing multiple conditions on the same field -in the same argument using the symbol operator syntax, conditions may be -combined using ``$eq`` or ``$regex`` operators, as appropriate, instead of -``$and``. - -Mongoid 7.3 behavior: - -.. code-block:: ruby - - Band.where(year: 2020, :year.gt => 1960) - # => - # #{"$eq"=>2020, "$gt"=>1960}} - # options: {} - # class: Band - # embedded: false> - - Band.where(name: /A/, :name.ne => 'Astral') - # => - # #{"$regex"=>/A/, "$ne"=>"Astral"}} - # options: {} - # class: Band - # embedded: false> - -Mongoid 7.2 behavior: - -.. code-block:: ruby - - Band.where(year: 2020, :year.gt => 1960) - # => - # #2020, "$and"=>[{"year"=>{"$gt"=>1960}}]} - # options: {} - # class: Band - # embedded: false> - - Band.where(name: /A/, :name.ne => 'Astral') - # => - # #/A/, "$and"=>[{"name"=>{"$ne"=>"Astral"}}]} - # options: {} - # class: Band - # embedded: false> - -The ``$regex`` operator is used when the value is a regular expression, i.e. -an instance of ``Regexp`` or ``BSON::Regexp::Raw`` classes. - -When using the ``not`` method with multiple conditions provided in the same -argument, the conditions are kept together and negated as a group. - -Mongoid 7.3 behavior: - -.. code-block:: ruby - - Band.not(year: 2020, :year.gt => 1960) - # => - # #[{"$nor"=>[{"year"=>{"$eq"=>2020, "$gt"=>1960}}]}]} - # options: {} - # class: Band - # embedded: false> - -Mongoid 7.2 behavior: - -.. code-block:: ruby - - Band.not(year: 2020, :year.gt => 1960) - # => - # #{"$ne"=>2020}, "$and"=>[{"$nor"=>[{"year"=>{"$gt"=>1960}}]}]} - # options: {} - # class: Band - # embedded: false> - - -New Embedded Matching Operators -------------------------------- - -Mongoid 7.3 adds support for bitwise operators, ``$comment``, ``$mod`` and -``$type`` operators when :ref:`embedded matching `. - - -Unaliasing ``id`` Field ------------------------ - -It is now possible to :ref:`remove the id alias in models `, -to make ``id`` a regular field. - - -``Mongoid.purge!`` and ``Mongoid.truncate`` take the global overrides into account ----------------------------------------------------------------------------------- - -Minor change: ``Mongoid.purge!`` and ``Mongoid.truncate!`` now consider global -overrides set with ``Mongoid.override_database`` and ``Mongoid.override_client``. - -Mongoid 7.3 behavior: - -.. code-block:: ruby - - Mongoid.override_database("some_other_db") - Band.create!(name: "Garage") - Band.count # => 1 - Mongoid.purge! # or Mongoid.truncate! - Band.count # => 0 - -Mongoid 7.2 behavior: - -.. code-block:: ruby - - Mongoid.override_database("some_other_db") - Band.create!(name: "Garage") - Band.count # => 1 - Mongoid.purge! # or Mongoid.truncate! - Band.count # => 1 - - -``update_one`` Warnings in ``upsert`` -------------------------------------- - -Mongoid 7.3.5 fixes incorrect usage of the driver's ``update_one`` method from -Mongoid's ``upsert`` method. Mongoid's ``upsert`` actually performs a -replacing upsert, and Mongoid 7.3.5 and later correctly call ``replace_one``. diff --git a/source/release-notes/mongoid-7.4.txt b/source/release-notes/mongoid-7.4.txt deleted file mode 100644 index dd3af5e7..00000000 --- a/source/release-notes/mongoid-7.4.txt +++ /dev/null @@ -1,584 +0,0 @@ -.. _mongoid-7.4: - -*********** -Mongoid 7.4 -*********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -This page describes significant changes and improvements in Mongoid 7.4. -The complete list of releases is available `on GitHub -`_ and `in JIRA -`_; -please consult GitHub releases for detailed release notes and JIRA for -the complete list of issues fixed in each release, including bug fixes. - -All behavior changes in Mongoid 7.4 must be explicitly requested by changing -the value of configuration options as detailed below. By default, -Mongoid 7.4 behaves the same as Mongoid 7.3. - - -Ruby Version Support --------------------- - -As of version 7.4, Mongoid supports Ruby 2.5+. -Support for Ruby 2.4 and earlier has been dropped. - - -Support for MongoDB 3.4 and Earlier Servers Deprecated ------------------------------------------------------- - -Mongoid 7.4 deprecates support for MongoDB 3.4 and earlier. -Mongoid 8 will require MongoDB 3.6 or newer. - - -Feature Flags Summary ---------------------- - -To ensure a stable upgrade path from Mongoid 7.3, Mongoid 7.4 -introduces feature flags which are further explained in the -sections below. - -To enable all new behavior in Mongoid 7.4, please use the following -:ref:`configuration options ` in your mongoid.yml file. -We recommend newly created apps to do this as well. - -.. code-block:: yaml - - development: - ... - options: - # Enable all new behavior in Mongoid 7.4 - legacy_triple_equals: false - object_id_as_json_oid: false - compare_time_by_ms: true - broken_aggregables: false - broken_updates: false - broken_and: false - broken_scoping: false - broken_alias_handling: false - legacy_pluck_distinct: false - - -Change ``===`` Operator To Match Ruby Semantics ------------------------------------------------ - -In Mongoid 7.4, the ``===`` operator on ``Mongoid::Document`` classes and -instances can be configured to behave the same way as it does in Ruby, -and is equivalent to calling ``is_a?`` on the right hand -side with the left hand side as the argument: - -.. code-block:: ruby - - ModelClass === instance - - # equivalent to: - instance.is_a?(ModelClass) - -In order to get this functionality, the ``Mongoid.legacy_triple_equals`` -option must be set to false. If it is set to true, which is the default for -Mongoid 7.4, the ``===`` operator will function as it did in Mongoid 7.3: -``===`` returned ``true`` for some cases when the equivalent Ruby -``===`` implementation returned false, as per the examples below. - -Mongoid 7.4 with ``Mongoid.legacy_triple_equals`` set to ``false`` behavior: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_many :members - end - - class CoverBand < Band - end - - class Member - include Mongoid::Document - - belongs_to :band - end - - band = Band.new - cover_band = CoverBand.new - - band === Band - # => false - - cover_band === Band - # => false - - Band === Band - # => false - - CoverBand === Band - # => false - - band.members === Array - # => false - - band.members === Mongoid::Association::Referenced::HasMany::Enumerable - # => false - -Mongoid 7.3 and 7.4 with ``Mongoid.legacy_triple_equals`` set to ``true`` -behavior: - -.. code-block:: ruby - - band === Band - # => true - - cover_band === Band - # => true - - Band === Band - # => true - - CoverBand === Band - # => true - - band.members === Array - # => true - - band.members === Mongoid::Association::Referenced::HasMany::Enumerable - # => true - -The standard invocation of ``===``, that is having the class on the left and -the instance on the right, works the same in Mongoid 7.4 as it did previously -and matches the core Ruby behavior: - -.. code-block:: ruby - - Band === band - # => true - - Band === cover_band - # => true - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.legacy_triple_equals`` option will - change to ``false``. - - -Return String ``_id`` Value (Hexadecimal) from ``BSON::ObjectId#as_json`` -------------------------------------------------------------------------- - -Mongoid 7.4 permits configuring the ``BSON::ObjectId#as_json`` method -to return the ``_id`` value as a hexadecimal string instead of the -``{"$oid" => "..."}`` hash it has returned in Mongoid 7.3 and previous -versions. - -When ``Mongoid.object_id_as_json_oid`` is set to ``false``, Mongoid will -delegate to ``bson-ruby`` implementation of ``BSON::ObjectId#as_json``. -In ``bson-ruby`` 4 the ``BSON::ObjectId#as_json`` method will continue -to return the hash ``{"$oid" => "..."}`` for backwards compatibility, but -in ``bson-ruby`` 5 the ``BSON::ObjectId#as_json`` method will return only -the hexadecimal ObjectId string. - -When ``Mongoid.object_id_as_json_oid`` is set to ``true``, Mongoid will -install an implementation of ``BSON::ObjectId#as_json`` which returns -the hash ``{"$oid" => "..."}`` as it did in Mongoid 7.3 and earlier. - -The behavior of ``as_json`` is summarized in the following table: - -.. list-table:: - :header-rows: 1 - :stub-columns: 1 - :class: compatibility-large no-padding - - * - ``Mongoid.object_id_as_json_oid`` value - - true - - false - - * - ``bson-ruby`` 4 - - ``{"$oid"=>"621ed7fda15d5d231594627c"}`` - - ``{"$oid"=>"621ed7fda15d5d231594627c"}`` - - * - ``bson-ruby`` 5 - - ``{"$oid"=>"621ed7fda15d5d231594627c"}`` - - ``"621ed7fda15d5d231594627c"`` - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.object_id_as_json_oid`` option will - change to ``false``. - - -Scoped Associations -------------------- - -Associations now support the ``:scope`` argument, yielding -:ref:`scoped associations `. - - -Compare Times With Millisecond Precision When Embedded Matching ---------------------------------------------------------------- - -Mongoid 7.4 with the ``Mongoid.compare_time_by_ms`` option set to ``true`` -will truncate the times to millisecond precision when comparing them while -performing embedded matching. - -Time objects in Ruby have nanosecond precision, whereas MongoDB server -can only store times with millisecond precision. Set the -``Mongoid.compare_time_by_ms`` option to ``true`` to truncate times to -millisecond precision when performing queries on already loaded embedded -associations (this is also called "embedded matching" and is done completely -in Ruby), to obtain the same query results when performing time comparisons -regardless of which documents are being queried. Setting this option to -``false`` will produce different results for queries on embedded associations -that are already loaded into memory vs queries on unloaded associations and -top-level models. - -The ``Mongoid.compare_time_by_ms`` option is set to ``false`` by default -in Mongoid 7.4 for backwards compatibility. - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.compare_time_by_ms`` option will - change to ``true``. - - -``count``, ``sum``, ``avg``, ``min``, ``max`` Ignore Sort If Not Limiting/Skipping ----------------------------------------------------------------------------------- - -The ``count``, ``sum``, ``avg``, ``min`` and ``max`` methods now omit the -sort stage from the generated aggregation pipeline if no skip or limit -is specified, because the results aren't affected by the sort order. -Example call that will now omit the sort stage and would potentially use -an index where it wouldn't before: - -.. code-block:: ruby - - Band.desc(:name).count - - -Return ``0`` When Aggregating Empty Result Sets ------------------------------------------------ - -Mongoid 7.4 with the ``Mongoid.broken_aggregables`` option set to ``false`` -will return ``0`` from the ``sum`` method over an empty result set, for example: - -.. code-block:: ruby - - Product.where(impossible_condition: true).sum(:price) - # => 0 - -Mongoid 7.3 and Mongoid 7.4 with the ``Mongoid.broken_aggregables`` option -set to ``true`` (the default) returns ``nil`` in this case. - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.broken_aggregables`` option will - change to ``false``. - - -Correct Update Behavior When Replacing Association --------------------------------------------------- - -Mongoid 7.4 with the ``Mongoid.broken_updates`` option set to ``false`` -will correctly persist an ``embeds_one`` association target that is set to nil -and then to a non-nil value, for example: - -.. code-block:: ruby - - class Canvas - include Mongoid::Document - - embeds_one :palette - end - - canvas.palette = palette - canvas.palette = nil - canvas.palette = palette - -In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.broken_aggregables`` -option set to ``true`` (the default), ``canvas.palette`` would be ``nil`` when -we would expect it to be ``palette``. - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.broken_updates`` option will - change to ``false`` - - -Correct Logical ``and`` Query Generation ----------------------------------------- - -Mongoid 7.4 with the ``Mongoid.broken_and`` option set to ``false`` -will preserve existing conditions when using ``and`` to add new conditions -to a query when the same operator is used on the same field multiple times. -For example, in the following query: - -.. code-block:: ruby - - Band.where(id: 1).and({year: {'$in' => [2020]}}, {year: {'$in' => [2021]}}).where(id: 2) - -Mongoid 7.4 with the ``Mongoid.broken_and`` option set to ``false`` will -generate the following criteria: - -.. code-block:: ruby - - #1, "year"=>{"$in"=>[2020]}, "$and"=>[{"year"=>{"$in"=>[2021]}}, {"_id"=>2}]} - options: {} - class: Band - embedded: false> - -In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.broken_and`` -option set to ``true`` (the default), the following criteria would be -generated instead which omit the {"$in" => [2021]} condition: - -.. code-block:: ruby - - 1, "year"=>{"$in"=>[2020]}, "$and"=>[{"_id"=>2}]} - options: {} - class: Band - embedded: false> - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.broken_and`` option will - change to ``false``. - - -Restore Parent Scope When Exiting ``with_scope`` Block ------------------------------------------------------- - -Mongoid 7.4 with the ``Mongoid.broken_scoping`` option set to ``false`` -will restore the parent scope when exiting a ``with_scope`` block. -For example: - -.. code-block:: ruby - - Band.with_scope(year: 2020) do - Band.with_scope(active: true) do - # ... - end - - # {year: 2020} condition is applied here - end - -In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.broken_scoping`` -option set to ``true`` (the default), once any ``with_scope`` block finishes, -all scopes are cleared: - -.. code-block:: ruby - - Band.with_scope(year: 2020) do - Band.with_scope(active: true) do - # ... - end - - # No scope is applied here - end - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.broken_scoping`` option will - change to ``false``. - - -Changes to ``distinct`` and ``pluck`` -------------------------------------- - -Respect Field Aliases In Embedded Documents When Using ``distinct`` and ``pluck`` -````````````````````````````````````````````````````````````````````````````````` - -When ``distinct`` and ``pluck`` are used with aliased fields in embedded -documents, the aliases can be expanded if the ``Mongoid.broken_alias_handling`` -option is set to ``false``. By default, for backwards compatibility, in -Mongoid 7.4 this option is set to true, yielding Mongoid 7.3 and earlier -behavior. Given the following definitions: - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_many :managers - end - - class Manager - include Mongoid::Document - embedded_in :band - - field :name, as: :n - end - -Mongoid 7.4 behavior with ``Mongoid.broken_alias_handling`` set to ``false``: - -.. code-block:: ruby - - # Expands out to "managers.n" in the query: - Band.distinct('managers.name') - Band.pluck('managers.name') - -Mongoid 7.3 and 7.4 with ``Mongoid.broken_alias_handling`` set to ``true`` behavior: - -.. code-block:: ruby - - # Sends "managers.name" without expanding the alias: - Band.distinct('managers.name') - Band.pluck('managers.name') - -.. note:: - - The alias expansion for top-level fields has already been done by Mongoid 7.3. - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.broken_alias_handling`` option will - change to ``false``. - - -Demongoize Values Returned from ``pluck`` and ``distinct`` -`````````````````````````````````````````````````````````` - -Mongoid 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``false`` -will demongoize values returned from the ``pluck`` and ``distinct`` methods. -Given the following definitions: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :sales, type: BigDecimal - end - - Band.create!(sales: "1E2") - Band.create!(sales: "2E2") - -Mongoid 7.4 behavior with ``Mongoid.legacy_pluck_distinct`` set to ``false``: - -.. code-block:: ruby - - Band.pluck(:sales) - # => [0.1e3, 0.2e3] - Band.distinct(:sales) - # => [0.1e3, 0.2e3] - - -In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.legacy_pluck_distinct`` -option set to ``true`` (the default), the value returned from the pluck and -distinct methods will not be demongoized. For example: - -.. code-block:: ruby - - Band.pluck(:sales) - # => ["1E2", "2E2"] - Band.distinct(:sales) - # => ["1E2", "2E2"] - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.legacy_pluck_distinct`` option will - change to ``false``. - - -Localized Fields with ``pluck`` and ``distinct`` -```````````````````````````````````````````````` -Mongoid 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``false`` -changes the behavior of using ``pluck`` and ``distinct`` with localized fields. -Now, when retrieving a localized field using these methods, the translation for -the current locale will be returned. To get the full translations hash the -``_translations`` field can be used. Given the following definitions: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, localize: true - end - - I18n.locale = :en - band = Band.create!(name: 'english-name') - I18n.locale = :de - band.name = 'deutsch-name' - band.save! - -Mongoid 7.4 behavior with ``Mongoid.legacy_pluck_distinct`` set to ``false``: - -.. code-block:: ruby - - Band.pluck(:name) - # => ["deutsch-name"] - Band.pluck(:name_translations) - # => [{"en"=>"english-name", "de"=>"deutsch-name"}, {"en"=>"english-name", "de"=>"deutsch-name"}] - -In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.legacy_pluck_distinct`` -option set to ``true`` (the default), inputting a localized field returns the -full translations hash. Inputting the ``_translations`` field will return ``nil``. -For example: - -.. code-block:: ruby - - Band.pluck(:name) - # => [{"en"=>"english-name", "de"=>"deutsch-name"}, {"en"=>"english-name", "de"=>"deutsch-name"}] - Band.pluck(:name_translations) - # => [nil, nil] - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.legacy_pluck_distinct`` option will - change to ``false``. - - -Embedded Fields with ``pluck`` -`````````````````````````````` -Mongoid 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``false`` -returns the embedded values themselves, i.e. not inside a hash. Given the -following definitions: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - embeds_one :label - end - - class Label - include Mongoid::Document - - embedded_in :band - field :sales, type: BigDecimal - end - -Mongoid 7.4 behavior with ``Mongoid.legacy_pluck_distinct`` set to ``false``: - -.. code-block:: ruby - - Band.pluck("label.sales") - # => [0.1e3] - -In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.legacy_pluck_distinct`` -option set to ``true`` (the default), plucking embedded attributes returns them -inside a hash. For example: - -.. code-block:: ruby - - Band.pluck("label.sales") - # => [{"sales"=>"1E2"}] - -.. note:: - - In Mongoid 8.0, the default value of the ``Mongoid.legacy_pluck_distinct`` option will - change to ``false``. - - -``update_one`` Warnings in ``upsert`` -------------------------------------- - -Mongoid 7.4.1 fixes incorrect usage of the driver's ``update_one`` method from -Mongoid's ``upsert`` method. Mongoid's ``upsert`` actually performs a -replacing upsert, and Mongoid 7.4.1 and later correctly call ``replace_one``. diff --git a/source/release-notes/mongoid-7.5.txt b/source/release-notes/mongoid-7.5.txt deleted file mode 100644 index b02da6ed..00000000 --- a/source/release-notes/mongoid-7.5.txt +++ /dev/null @@ -1,273 +0,0 @@ -.. _mongoid-7.5: - -*********** -Mongoid 7.5 -*********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -This page describes significant changes and improvements in Mongoid 7.5. -The complete list of releases is available `on GitHub -`_ and `in JIRA -`_; -please consult GitHub releases for detailed release notes and JIRA for -the complete list of issues fixed in each release, including bug fixes. - - -Ruby, JRuby and Rails Version Support -------------------------------------- - -Mongoid 7.5 deprecates support for Ruby 2.5, JRuby 9.2 and Rails 5.1. -Mongoid 8 will require Ruby 2.6 or newer, JRuby 9.3 or newer and Rails 5.2 or -newer. - - -Feature Flags Summary ---------------------- - -To ensure a stable upgrade path from Mongoid 7.4, Mongoid 7.5 -introduces feature flags which are further explained in the -sections below. - -To enable all new behavior in Mongoid 7.5, please use the following -:ref:`configuration options ` in your mongoid.yml file. -We recommend newly created apps to do this as well. - -.. code-block:: yaml - - development: - ... - options: - # Enable all new behavior in Mongoid 7.5 - legacy_attributes: false - overwrite_chained_operators: false - -In addition, please refer to the release notes of earlier 7.x versions for -feature flags introduced in each version. For clarity, Mongoid 7.5 does -not switch the behavior of any previously introduced feature flag. - - -Implemented ``Criteria#take/take!`` Method ------------------------------------------- - -Mongoid 7.5 introduces the ``#take`` method which returns a document -or a set of documents from the database without ordering by ``_id``: - -.. code:: ruby - - class Band - include Mongoid::Document - end - - Band.create! - Band.create! - - Band.take - # => # - Band.take(2) - # => [ #, # ] - -If a parameter is given to ``#take``, an array of documents is returned. If no parameter is -given, a singular document is returned. - -The ``#take!`` method functions the same as calling ``#take`` without arguments, -but raises an DocumentNotFound error instead of returning nil if no documents -are found. - -.. code:: ruby - - Band.take! - # => # - -Note that the ``#take/take!`` methods do not apply a sort to the view before -retrieving the documents from the database, and therefore they may return different -results than the ``#first`` and ``#last`` methods. ``#take`` is equivalent to -calling ``#first`` or ``#last`` with the ``{ id_sort: :none }`` option. This -option has been deprecated in Mongoid 7.5 and it is recommended to use ``#take`` -instead going forward. Support for the ``:id_sort`` option will be dropped in -Mongoid 8. - - -Force the ``attributes`` Method to Always Return a ``Hash`` ------------------------------------------------------------ - -Mongoid 7.5 with the ``Mongoid.legacy_attributes`` option set to ``false`` -will always return a ``Hash`` when calling the ``attributes`` method. -For example: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name - end - - band = Band.create!(name: "The Rolling Stones") - p band.attributes.class - # => Hash - - band = Band.first - p band.attributes.class - # => Hash - -In Mongoid 7.4 and earlier, and in 7.5 with the ``Mongoid.legacy_attributes`` -option set to ``true``, the ``attributes`` method on a document will return a -``BSON::Document`` when retrieving that document from the database, but will -return a ``Hash`` when instantiating a new document: - -.. code-block:: ruby - - band = Band.create!(name: "The Rolling Stones") - p band.attributes.class - # => Hash - - band = Band.first - p band.attributes.class - # => BSON::Document - - -Deprecate ``:id_sort`` Option and Support ``limit`` on ``#first/last`` ----------------------------------------------------------------------- - -Mongoid 7.5 deprecates the ``:id_sort`` keyword argument for the -``Criteria#first`` and ``Criteria#last`` methods. Please use ``Criteria#take`` -to retrieve documents without sorting by id. - -The ``first`` and ``last`` methods now take the number of documents to return -as a positional argument, mirroring the functionality of Ruby's ``Enumerable`` -method and ActiveRecord's ``first`` and ``last`` methods. Both invocations -(with limit as a positional arguments and with the ``:id_sort`` option) remain -supported in Mongoid 7.x, but the ``:id_sort`` invocation will be removed in -Mongoid 8. - -.. code:: ruby - - Band.first - # => # - Band.first(2) - # => [ #, # ] - Band.last - # => # - Band.last(2) - # => [#, #] - -When providing a limit, ``#first/last`` will return a list of documents, and -when not providing a limit (or providing ``nil``), a single document will be -returned. - -Note that the ``#first/last`` methods apply a sort on ``_id``, which can -cause performance issues. To get a document without sorting first, use the -``Critera#take`` method. - - -Combine Chained Operators Using ``and`` Instead of ``override`` ---------------------------------------------------------------- - -Mongoid 7.5 with the ``Mongoid.overwrite_chained_operators`` option set to ``false`` -will combine conditions that use the same operator and field using an ``and``. -For example, in the following query: - -.. code-block:: ruby - - Band.ne(name: "The Rolling Stones").ne(name: "The Beatles") - -Mongoid 7.5 with the ``Mongoid.overwrite_chained_operators`` option set to ``false`` -will generate the following criteria: - -.. code-block:: ruby - - #{"$ne"=>"The Rolling Stones"}, "$and"=>[{"name"=>{"$ne"=>"The Beatles"}}]} - options: {} - class: Band - embedded: false> - -In Mongoid 7.4 and earlier, and in 7.5 with the ``Mongoid.overwrite_chained_operators`` -option set to ``true``, the following criteria would be generated instead which -overwrites the first condition: - -.. code-block:: ruby - - #{"$ne"=>"The Beatles"}} - options: {} - class: Band - embedded: false> - -The following functions are affected by this change: - -- ``eq`` -- ``elem_match`` -- ``gt`` -- ``gte`` -- ``lt`` -- ``lte`` -- ``mod`` -- ``ne`` -- ``near`` -- ``near_sphere`` - -.. note:: - - In Mongoid 7.5 with the ``Mongoid.overwrite_chained_operators`` option set to - ``false``, nested keys in the generated selector will always be strings, - whereas in Mongoid 7.4 and earlier, and in 7.5 with the - ``Mongoid.overwrite_chained_operators`` option set to ``true``, nested keys in - the selector can be strings or symbols, depending on what was passed to the - operator. - - -``pluck`` Usage of ``map`` Deprecated -------------------------------------- - -Mongoid 7.5 deprecates the usage of ``map`` as pluck, as in the following -example: - -.. code-block:: ruby - - Band.all.map(:name) - - # Equivalent to: - Band.pluck(:name) - -This usage will no longer be supported in Mongoid 8, which will not accept -arguments to ``map``. - - -``Mongoid::Criteria`` cache deprecated --------------------------------------- - -The ability to cache individual criteria objects has been deprecated in Mongoid -7.5 and will be dropped in Mongoid 8. - -In order to get caching functionality, enable the Mongoid QueryCache. See the -section on :ref:`QueryCache ` for more details. - - -``Array#update_values`` and ``Hash#update_values`` deprecated -------------------------------------------------------------- - -The ``Array#update_values`` and ``Hash#update_values`` methods are deprecated in -Mongoid 7.5. It is recommended to use ActiveSupport's ``transform_values!`` -method instead. - - -``Document#to_a`` deprecated ----------------------------- - -The ``Document#to_a`` method is deprecated in Mongoid 7.5. - - -``update_one`` Warnings in ``upsert`` -------------------------------------- - -Mongoid 7.5 fixes incorrect usage of the driver's ``update_one`` method from -Mongoid's ``upsert`` method. Mongoid's ``upsert`` actually performs a -replacing upsert, and Mongoid 7.5 correctly calls ``replace_one``. diff --git a/source/release-notes/mongoid-8.0.txt b/source/release-notes/mongoid-8.0.txt deleted file mode 100644 index 553494b1..00000000 --- a/source/release-notes/mongoid-8.0.txt +++ /dev/null @@ -1,866 +0,0 @@ -.. _mongoid-8.0: - -*********** -Mongoid 8.0 -*********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -This page describes significant changes and improvements in Mongoid 8.0. -The complete list of releases is available `on GitHub -`_ and `in JIRA -`_; -please consult GitHub releases for detailed release notes and JIRA for -the complete list of issues fixed in each release, including bug fixes. - - -Support for MongoDB 3.4 and Earlier Servers Dropped ---------------------------------------------------- - -Mongoid 8 requires MongoDB 3.6 or newer. Earlier server versions are not -supported. - - -Support for Ruby 2.5 Dropped ----------------------------- - -Mongoid 8 requires Ruby 2.6 or newer. Earlier Ruby versions are not supported. - - -Support for Rails 5.1 Dropped ------------------------------ - -Mongoid 8 requires Rails 5.2 or newer. Earlier Rails versions are not supported. - - -Default Option Values Changed ------------------------------ - -**Breaking change:** The following options have had their default values -changed in Mongoid 8.0: - -- ``:broken_aggregables`` => ``false`` -- ``:broken_alias_handling`` => ``false`` -- ``:broken_and`` => ``false`` -- ``:broken_scoping`` => ``false`` -- ``:broken_updates`` => ``false`` -- ``:compare_time_by_ms`` => ``true`` -- ``:legacy_attributes`` => ``false`` -- ``:legacy_pluck_distinct`` => ``false`` -- ``:legacy_triple_equals`` => ``false`` -- ``:object_id_as_json_oid`` => ``false`` -- ``:overwrite_chained_operators`` => ``false`` - -Please refer to :ref:`configuration option ` for -the description and effects of each of these options. - - -``Decimal128``-backed ``BigDecimal`` Fields -------------------------------------------- - -Mongoid 8 introduces the ``map_big_decimal_to_decimal128`` feature flag, which -allows values assigned to a field of type ``BigDecimal`` to be stored as type -``String`` in the database for compatibility with Mongoid 7 and earlier. In -Mongoid 8 by default (with this feature flag turned on), values assigned to -fields of type ``BigDecimal`` are stored in the database as type -``BSON::Decimal128``. In Mongoid 7 and earlier, and in Mongoid 8 with this -feature flag turned off, values assigned to fields of type ``BigDecimal`` are -stored as Strings. See the section on :ref:`BigDecimal Fields ` -for more details. - - -Storing/Retrieving/Evolving Uncastable Values ---------------------------------------------- - -**Breaking change:** In Mongoid 8, Mongoid standardizes the storing, retrieving -and evolving of "uncastable values." On attempting to read or write an -uncastable value, a ``nil`` is returned or written instead. When attempting to -evolve an uncastable value, the inputted value is returned. See the section on -:ref:`Uncastable Values ` for more details. - -Some ``mongoize``, ``demongoize`` and ``evolve`` methods were also changed to -perform consistently with rails and the other ``mongoize``, ``demongoize`` and -``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:: - - The ``demongoize`` methods on container objects (i.e. Hash, Array) have not - changed to prevent bugs when modifying and saving those objects. See - https://jira.mongodb.org/browse/MONGOID-2951 for a longer discussion on these - bugs. - -Changes to the ``attributes_before_type_cast`` Hash ---------------------------------------------------- - -The ``attributes_before_type_cast`` hash has been changed to function more like -ActiveRecord: - -- On instantiation of a new model (without parameters), the - ``attributes_before_type_cast`` hash has the same contents as the - ``attributes`` hash. If parameters are passed to the initializer, those - values will be stored in the ``attributes_before_type_cast`` hash before - they are ``mongoized``. -- When assigning a value to the model, the ``mongoized`` value (i.e. when - assiging '1' to an Integer field, it is ``mongoized`` to 1) is stored in - the ``attributes`` hash, whereas the raw value (i.e. '1') is stored in the - ``attributes_before_type_cast`` hash. -- When saving, creating (i.e. using the ``create!`` method), or reloading the - model, the ``attributes_before_type_cast`` hash is reset to have the same - contents as the ``attributes`` hash. -- When reading a document from the database, the ``attributes_before_type_cast`` - hash contains the attributes as they appear in the database, as opposed to - their ``demongoized`` form. - - -Order of Callback Invocation ----------------------------- - -**Breaking change:** Mongoid 8.0 changes the order of _create and _save callback -invocation for documents with associations. - -Referenced associations (``has_one`` and ``has_many``): - -.. 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``): - -.. 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`` --------------------------------------------------------------------------- - -When updating documents, it is now possible to get updated attribute values -in ``after_*`` callbacks. This follows ActiveRecord/ActiveModel behavior. - -.. code-block:: ruby - - class Cat - include Mongoid::Document - - field :age, type: Integer - - after_save do - p self - p attribute_was(:age) - end - end - - a = Cat.create! - a.age = 2 - a.save! - -Mongoid 8.0 output: - -.. code-block:: ruby - - # - 2 - - -Mongoid 7 output: - -.. code-block:: ruby - - # - nil - -Notice that in 7 ``attribute_was(:age)`` returns the old attribute value, -while in 8.0 ``attribute_was(:age)`` returns the new value. - - -``*_previously_was``, ``previously_new_record?``, and ``previously_persisted?`` helpers ---------------------------------------------------------------------------------------- - -Mongoid 8.0 introduces ActiveModel-compatible ``*_previously_was`` helpers, -as well as ActiveRecord-compatible ``previously_new_record?`` and -``previously_persisted?`` helpers: - -.. code-block:: ruby - - class User - include Mongoid::Document - - field :name, type: String - field :age, type: Integer - end - - user = User.create!(name: 'Sam', age: 18) - user.previously_new_record? # => true - - user.name = "Nick" - user.save! - user.name_previously_was # => "Sam" - user.age_previously_was # => 18 - user.previously_new_record? # => false - - user.destroy - user.previously_persisted? # => true - - -Unknown Field Type Symbols/Strings Prohibited ---------------------------------------------- - -**Breaking change:** Mongoid 8 prohibits using symbols and strings as field -types when these symbols and strings do not map to a known type. Previously -such usage would create a field of type ``Object``. - -Mongoid 8 behavior: - -.. code-block:: ruby - - class User - include Mongoid::Document - - field :name, type: :bogus - # => raises Mongoid::Errors::InvalidFieldType - end - -Mongoid 7 behavior: - -.. code-block:: ruby - - class User - include Mongoid::Document - - field :name, type: :bogus - # Equivalent to: - field :name - end - - -``any_of`` Adds Multiple Arguments As Top-Level Conditions ----------------------------------------------------------- - -**Breaking change:** When ``any_of`` is invoked with multiple conditions, the -conditions are now added to the top level of the criteria, same as when -``any_of`` is invoked with a single condition. Previously when multiple -conditions were provided, and the criteria already had an ``$or`` operator, -the new conditions would be added to the existing ``$or`` as an additional -branch. - -Mongoid 8.0 behavior: - -.. code-block:: ruby - - Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}). - any_of({members: 2}, {last_tour: 1995}) - # => - # #[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], - # "$and"=>[{"$or"=>[{"members"=>2}, {"last_tour"=>1995}]}]} - # options: {} - # class: Band - # embedded: false> - - Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}).any_of({members: 2}) - # => - # #[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], "members"=>2} - # options: {} - # class: Band - # embedded: false> - -Mongoid 7 behavior: - -.. code-block:: ruby - - Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}). - any_of({members: 2}, {last_tour: 1995}) - # => - # #[{"name"=>"The Rolling Stones"}, {"founded"=>1990}, - # {"members"=>2}, {"last_tour"=>1995}]} - # options: {} - # class: Band - # embedded: false> - - Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}).any_of({members: 2}) - # => - # #[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], "members"=>2} - # options: {} - # class: Band - # embedded: false> - - -``#pluck`` on Embedded Criteria Returns ``nil`` Values ------------------------------------------------------- - -Mongoid 8 fixes a bug where calling ``#pluck`` on a Mongoid::Criteria -for embedded documents discarded nil values. This behavior was -inconsistent with both the ``#pluck`` method in ActiveSupport and -with how ``#pluck`` works when reading documents from the database. - -Mongoid 8.0 behavior: - -.. code-block:: ruby - - class Address - include Mongoid::Document - - embedded_in :mall - - field :street, type: String - end - - class Mall - include Mongoid::Document - - embeds_many :addresses - end - - mall = Mall.create! - mall.addresses.create!(street: "Elm Street") - mall.addresses.create!(street: nil) - - # Pluck from embedded document criteria - mall.addresses.all.pluck(:street) - #=> ['Elm Street', nil] - -Mongoid 7 behavior, given the same setup: - -.. code-block:: ruby - - # Pluck from embedded document criteria - mall.addresses.all.pluck(:street) - #=> ['Elm Street'] - -For clarity, the following behavior is unchanged from Mongoid 7 to Mongoid 8.0: - -.. code-block:: ruby - - # Pluck from database - Mall.all.pluck('addresses.street') - #=> [ ['Elm Street', nil] ] - - # Pluck using ActiveSupport Array#pluck - mall.addresses.pluck(:street) - #=> ['Elm Street', nil] - - -Replaced ``Mongoid::Criteria#geo_spacial`` with ``#geo_spatial`` ----------------------------------------------------------------- - -The previously deprecated ``Mongoid::Criteria#geo_spacial`` method has been -removed in Mongoid 8. It has been replaced one-for-one with ``#geo_spatial`` -which was added in Mongoid 7.2.0. - - -Implemented ``.tally`` method on ``Mongoid#Criteria`` ------------------------------------------------------ - -Mongoid 8 implements the ``.tally`` method on ``Mongoid#Criteria``. ``tally`` -takes a field name as a parameter and returns a mapping from values to their -counts. For example, take the following model: - -.. code:: - - class User - include Mongoid::Document - field :age - end - -and the following documents in the database: - -.. code:: - - { _id: 1, age: 21 } - { _id: 2, age: 21 } - { _id: 3, age: 22 } - -Calling ``tally`` on the age field yields the following: - -.. code:: - - User.tally("age") - # => { 21 => 2, 22 => 1 } - -The ``tally`` method accepts the dot notation and field aliases. It also -allows for tallying localized fields. - - -Implemented ``.pick`` method on ``Mongoid#Criteria`` ------------------------------------------------------ - -Mongoid 8 implements the ``.pick`` method on ``Mongoid#Criteria``. ``pick`` -takes one or more field names as a parameter and returns the values for those -fields from one document. Consider the following model: - -.. code:: - - class User - include Mongoid::Document - field :age - end - -and the following documents in the database: - -.. code:: - - { _id: 1, age: 21 } - { _id: 2, age: 21 } - { _id: 3, age: 22 } - -Calling ``pick`` on the age field yields the following: - -.. code:: - - User.pick(:age) - # => 21 - -This method does not apply a sort to the documents, so it will not necessarily -return the values from the first document. - -The ``pick`` method accepts the dot notation and field aliases. It also -allows for picking localized fields. - - -``find`` delegates to ``Enumerable#find`` when given a block ------------------------------------------------------------- - -When given a block, without ``_id`` arguments, ``find`` delegates to -``Enumerable#find``. Consider the following model: - -.. code:: - - class Band - include Mongoid::Document - field :name, type: String - end - - Band.create!(name: "Depeche Mode") - Band.create!(name: "The Rolling Stones") - -Calling ``find`` with a block returns the first document for which the block -returns ``true``: - -.. code:: - - Band.find do |b| - b.name == "Depeche Mode" - end - # => # - - -No Longer Persisting Documents with Invalid ``belongs_to`` Associations ------------------------------------------------------------------------ - -In Mongoid 8, if an invalid document is assigned to a ``belongs_to`` association, -and the base document is saved, if the ``belongs_to`` association had the -option ``optional: false`` or ``optional`` is unset and the global flag -``belongs_to_required_by_default`` is true, neither the document nor the -associated document will be persisted. For example, given the following -models: - -.. code:: - - class Parent - include Mongoid::Document - has_one :child - field :name - validates :name, presence: true - end - - class Child - include Mongoid::Document - - belongs_to :parent, autosave: true, validate: false, optional: false - end - - child = Child.new - parent = Parent.new - child.parent = parent # parent is invalid, it does not have a name - child.save - -In this case, both the child and the parent will not be persisted. - -.. note:: - If ``save!`` were called, a validation error would be raised. - -If optional is false, then the Child will be persisted with a parent_id, but the -parent will not be persisted: - -.. code:: - - child = Child.new - parent = Parent.new - child.parent = parent # parent is invalid, it does not have a name - child.save - - p Child.first - # => - p Parent.first - # => nil - -If you want the functionality of neither document being persisted in Mongoid 7 or 8 -and earlier, the ``validate: true`` option can be set on the association. If -you want the functionality of only the Child being persisted, the ``validate: -false`` option can be set on the association. - - -Update Local HABTM Association Correctly ----------------------------------------- - -In Mongoid 8, when pushing persisted elements to a HABTM association, the -association will now update correctly without requiring a reload. -For example: - -.. code:: - - class User - include Mongoid::Document - has_and_belongs_to_many :posts - end - - class Post - include Mongoid::Document - has_and_belongs_to_many :users - end - - user1 = User.create! - user2 = User.create! - - post = user1.posts.create! - - p post.users.length - # => 1 - - post.users << user2 - - p post.users.length - # => 1 in Mongoid 7, 2 in Mongoid 8 - - p post.reload.users.length - # => 2 - -As you can see from this example, after pushing ``user2`` to the users array, -Mongoid 8 correctly updates the number of elements without requiring a reload. - - -Repaired Storing Strings in BSON::Binary fields ------------------------------------------------ - -**Breaking change:** In Mongoid 8, storing a String in a field of type -``BSON::Binary`` will be stored in and returned from the database as a -``BSON::Binary``. Prior to Mongoid 8 it was stored and returned as a String: - -.. code:: - - class Registry - include Mongoid::Document - field :data, type: BSON::Binary - end - - registry = Registry.create!(data: "data!") - p registry.data - # => Mongoid 7: "data!" - # => Mongoid 8: - - registry = Registry.find(registry.id) - p registry.data - # => Mongoid 7: "data!" - # => Mongoid 8: - - -Removed ``Document#to_a`` Method --------------------------------- - -The previously deprecated ``Document#to_a`` method has been removed in -Mongoid 8. - - -Removed ``:drop_dups`` Option from Indexes ------------------------------------------- - -The ``:drop_dups`` option has been removed from the ``index`` macro. This option -was specific to MongoDB server 2.6 and earlier, which Mongoid no longer supports. - - -Removed ``Mongoid::Errors::EagerLoad`` Exception Class ------------------------------------------------------- - -The previously deprecated ``Mongoid::Errors::EagerLoad`` exception class -has been removed in Mongoid 8. It has not been used by Mongoid since -version 7.1.1 when eager loading for polymorphic ``belongs_to`` associations -was implemented. - - -Removed Deprecated Constants ----------------------------- - -Mongoid 8 removes the following deprecated constants that are not expected -to have been used outside of Mongoid: - -- ``Mongoid::Extensions::Date::EPOCH`` -- ``Mongoid::Extensions::Time::EPOCH`` -- ``Mongoid::Factory::TYPE`` - - -Removed ``Array#update_values`` and ``Hash#update_values`` methods ------------------------------------------------------------------- - -The previously deprecated ``Array#update_values`` and ``Hash#update_values`` -methods have been removed in Mongoid 8. - - -Deprecated the ``geoHaystack``, ``geoSearch`` Options ------------------------------------------------------ - -The ``geoHaystack`` and ``geoSearch`` options on indexes have been deprecated. - - -``:id_sort`` Option on ``#first/last`` Removed ----------------------------------------------- - -Support for the ``:id_sort`` option on the ``#first`` and ``#last`` options has -been dropped. These methods now only except a limit as a positional argument. - - -Mongoid::Criteria Cache Removed -------------------------------- - -Support for individually caching criteria objects has been dropped in Mongoid 8. - -In order to get caching functionality, enable the Mongoid Query Cache. See the -section on :ref:`Query Cache ` for more details. - -New Caching Behavior of ``_ids`` Methods ------------------------------------------------------ - -If you call an ``_ids`` method, such as ``member_ids``, on an -association previously loaded into memory, Mongoid 8.0 uses the in-memory collection to satisfy the query -instead of issuing a new query to the database. - -For example, the following code calls the ``member_ids`` method and uses the in-memory -collection to retrieve the ``id`` values. The ``id`` of the ``new_member`` is not included -in the output: - -.. code-block:: ruby - - ids = band.member_ids - # ... - - new_member = Member.create(band_id: band.id) - - # Prior to 8.0, the method output includes the newly-created member's id. - # Starting in 8.0, the output does not include the newly-created member's id. - p band.member_ids - -You can force an ``_ids`` method to issue a new database query -by resetting the association before calling ``_ids``, as shown -in the following code: - -.. code-block:: ruby - - band.members.reset - - # Queries the database rather than the in-memory collection - p band.member_ids - -You can also issue a new database query by reloading the parent record -and calling the ``_ids`` method, as shown in the following code: - -.. code-block:: ruby - - band.reload.member_ids \ No newline at end of file diff --git a/source/release-notes/mongoid-8.1.txt b/source/release-notes/mongoid-8.1.txt deleted file mode 100644 index 5c69df80..00000000 --- a/source/release-notes/mongoid-8.1.txt +++ /dev/null @@ -1,431 +0,0 @@ -.. _mongoid-8.1: - -*********** -Mongoid 8.1 -*********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -This page describes significant changes and improvements in Mongoid 8.1. -The complete list of releases is available `on GitHub -`_ and `in JIRA -`_; -please consult GitHub releases for detailed release notes and JIRA for -the complete list of issues fixed in each release, including bug fixes. - -Added ``load_async`` method on ``Criteria`` to asynchronously load documents ----------------------------------------------------------------------------- - -The new ``load_async`` method on ``Criteria`` allows :ref:`running database queries asynchronously `. - - -Added ``attribute_before_last_save``, ``saved_change_to_attribute``, ``saved_change_to_attribute?``, and ``will_save_change_to_attribute?`` methods ----------------------------------------------------------------------------------------------------------------------------------------------------- - -These new methods behave identically to corresponding methods -from ``ActiveRecord::AttributeMethods::Dirty``. The methods are particularly useful in -callbacks: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - field :name, type: String - - before_save do - puts "attribute_was(:name): #{attribute_was(:name)}" - puts "attribute_before_last_save(:name): #{attribute_before_last_save(:name)}" - puts "will_save_change_to_attribute?(:name): #{will_save_change_to_attribute?(:name)}" - end - - after_save do - puts "attribute_was(:name): #{attribute_was(:name)}" - puts "attribute_before_last_save(:name): #{attribute_before_last_save(:name)}" - puts "saved_change_to_attribute(:name): #{saved_change_to_attribute(:name)}" - puts "attribute_changed?(:name): #{attribute_changed?(:name)}" - puts "saved_change_to_attribute?(:name): #{saved_change_to_attribute?(:name)}" - end - end - - person = Person.create(name: 'John') - # - # before_save - # - ## attribute_was(:name): nil - ## attribute_before_last_save(:name): nil - ## will_save_change_to_attribute?(:name): true - # - # after_save - # - ## attribute_was(:name): John => New value - ## attribute_before_last_save(:name): nil => Value before save - ## saved_change_to_attribute(:name): [nil, "John"] => Both values - ## attribute_changed?(:name): false - ## saved_change_to_attribute?(:name): true => Correctly indicates that the change for :name was saved - - person.name = 'Jane' - person.save - # - # before_save - # - ## attribute_was(:name): John => attribute_was not look back before the last save - ## attribute_before_last_save(:name): nil => value before the last save - ## will_save_change_to_attribute?(:name): true - # - # after_save - # - ## attribute_was(:name): Jane => New value - ## attribute_before_last_save(:name): John => Value before save - ## saved_change_to_attribute(:name): ["John", "Jane"] => Both values - ## attribute_changed?(:name): false - ## saved_change_to_attribute?(:name): true => Correctly indicates that the change for :name was saved - -For all of the new methods there are also shorter forms created dynamically, e.g. -``attribute_before_last_save(:name)`` is equivalent to ``name_before_last_save``, -``saved_change_to_attribute(:name)`` is equivalent to ``saved_change_to_name``, -``saved_change_to_attribute?(:name)`` is equivalent to ``saved_change_to_name?``, -and ``will_save_change_to_attribute?(:name)`` is equivalent to ``will_save_change_to_name?``. - - -Deprecated ``use_activesupport_time_zone`` config option --------------------------------------------------------- - -The config option ``use_activesupport_time_zone`` has been deprecated. -Beginning in Mongoid 9.0, it will be ignored and always behave as true. -Since ``use_activesupport_time_zone`` currently defaults to true, it is -safe to remove from your config file at this time. - - -Configuration DSL No Longer Requires an Argument to its Block -------------------------------------------------------------- - -It is now possible to use ``Mongoid.configure`` without -providing an argument to its block: - -.. code-block:: ruby - - Mongoid.configure do - connect_to("mongoid_test") - - # Use config method when assigning variables - config.preload_models = true - -Note that ``configure`` will continue to support a block argument. -The following is equivalent to the above: - -.. code-block:: ruby - - Mongoid.configure do |config| - config.connect_to("mongoid_test") - - config.preload_models = true - - -Added ``Mongoid::Criteria`` finder methods ------------------------------------------- - -Mongoid 8.1 implements several finder methods on ``Mongoid::Criteria``: - -- ``first!`` -- ``last!`` -- ``second/second!`` -- ``third/third!`` -- ``fourth/fourth!`` -- ``fifth/fifth!`` -- ``second_to_last/second_to_last!`` -- ``third_to_last/third_to_last!`` - -When no documents are found, methods without a bang (!) return nil, and methods -with a bang (!) raise an error: - -.. code:: ruby - - Band.none.first - # => nil - - Band.none.first! - # => raise Mongoid::Errors::DocumentNotFound - - -Added ``:touch`` option to ``#save`` ------------------------------------- - -Support for the ``:touch`` option has been added to the ``#save`` and ``#save!`` -methods. When this option is false, the ``updated_at`` field on the saved -document and all of it's embedded documents will not be updated with the -current time. When this option is true or unset, the ``updated_at`` field will -be updated with the current time. - -If the document being saved is a new document (i.e. it has not yet been -persisted to the database), then the ``:touch`` option will be ignored, and the -``updated_at`` (and ``created_at``) fields will be updated with the current -time. - - -Added Version Based Default Configuration ------------------------------------------ - -Mongoid 8.1 has added the ability to set the default configurations for a -specific version: - -.. code:: ruby - - Mongoid.configure do |config| - config.load_defaults 8.0 - end - -This is helpful for upgrading between versions. See the section on -:ref:`Version Based Default Configuration ` for more details on -how to use this feature to make upgrading between Mongoid versions easier. - - -Added ``:present`` option to localized fields ---------------------------------------------- - -The ``:present`` option was added to localized fields for removing blank values -from the ``_translations`` hash: - -.. code-block:: ruby - - class Product - include Mongoid::Document - field :description, localize: :present - end - -See the section on :ref:`Localize :present Field Option ` for -more details on how these are used. - - -Added ``:to`` and ``:from`` options to ``attribute_changed?`` -------------------------------------------------------------- - -Mongoid 8.1 adds the ``:to`` and ``:from`` options on the ``attribute_changed?`` -method. These options can be used to inquire whether the attribute has been changed -to or from a certain value: - -.. code: - - class Person - include Mongoid::Document - field :name, type: String - end - - person = Person.create!(name: "Trout") - person.name = "Ohtani" - - person.name_changed? - # => true - person.name_changed?(from: "Trout") - # => true - person.name_changed?(to: "Ohtani") - # => true - person.name_changed?(from: "Trout", to: "Ohtani") - # => true - person.name_changed?(from: "Trout", to: "Fletcher") - # => false - - -Allowed ``store_in`` to be called on subclasses ------------------------------------------------ - -Starting in Mongoid 8.1, subclasses can now specify which collection its -documents should be stored in using the ``store_in`` macro: - -.. code:: ruby - - class Shape - include Mongoid::Document - store_in collection: :shapes - end - - class Circle < Shape - store_in collection: :circles - end - - class Square < Shape - store_in collection: :squares - end - -Previously, an error was raised if this was done. See the section on -:ref:`Inheritance Persistence Context ` -for more details. - - -Added ``readonly!`` method and ``legacy_readonly`` feature flag ---------------------------------------------------------------- - -Mongoid 8.1 changes the meaning of read-only documents. In Mongoid 8.1 with -this feature flag turned off (``false``), a document becomes read-only when calling the -``readonly!`` method: - -.. code:: ruby - - band = Band.first - band.readonly? # => false - band.readonly! - band.readonly? # => true - band.name = "The Rolling Stones" - band.save # => raises ReadonlyDocument error - -With this feature flag turned off, a ``ReadonlyDocument`` error will be -raised when destroying or deleting, as well as when saving or updating. - -Prior to Mongoid 8.1 and in 8.1 with the ``legacy_readonly`` feature flag -turned on (``true``), documents become read-only when they are projected (i.e. using -``#only`` or ``#without``). - -.. code:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :genre, type: String - end - - band = Band.only(:name).first - band.readonly? # => true - band.destroy # => raises ReadonlyDocument error - -Note that with this feature flag on, a ``ReadonlyDocument`` error will only be -raised when destroying or deleting, and not on saving or updating. See the -section on :ref:`Read-only Documents ` for more details. - - -Added method parameters to ``#exists?`` ---------------------------------------- - -Mongoid 8.1 introduces method paramters to the ``Contextual#exists?`` method. -An _id, a hash of conditions, or ``false``/``nil`` can now be included: - -.. code:: ruby - - Band.exists? - Band.exists?(name: "The Rolling Stones") - Band.exists?(BSON::ObjectId('6320d96a3282a48cfce9e72c')) - Band.exists?(false) # always false - Band.exists?(nil) # always false - - -Added ``:replace`` option to ``#upsert`` ----------------------------------------- - -Mongoid 8.1 adds the ``:replace`` option to the ``#upsert`` method. This option -is ``false`` by default. - -In Mongoid 8 and earlier, and in Mongoid 8.1 when passing ``replace: true`` -(the default) the upserted document will overwrite the current document in the -database if it exists. Consider the following example: - -.. code:: ruby - - existing = Player.create!(name: "Juan Soto", age: 23, team: "WAS") - - player = Player.new(name: "Juan Soto", team: "SD") - player.id = existing.id - player.upsert # :replace defaults to true in 8.1 - - p Player.find(existing.id) - # => - -As you can see, the value for the ``:age`` field was dropped, because the -upsert replaced the entire document instead of just updating it. If we take the -same example and set ``:replace`` to ``false``, however: - -.. code:: ruby - - player.upsert(replace: false) - - p Player.find(existing.id) - # => - -This time, the value for the ``:age`` field is maintained. - -.. note:: - - The default for the ``:replace`` option will be changed to ``false`` in - Mongoid 9.0, therefore it is recommended to explicitly specify this option - while using ``#upsert`` in 8.1 for easier upgradability. - - -Allow Hash Assignment to ``embedded_in`` ----------------------------------------- - -Mongoid 8.1 allows the assignment of a hash to an ``embedded_in`` association. -On assignment, the hash will be coerced into a document of the class of the -association that it is being assigned to. This functionality already exists -for ``embeds_one`` and ``embeds_many`` associations. Consider the following -example: - -.. code:: ruby - - class Band - include Mongoid::Document - field :name, type: String - embeds_many :albums - end - - class Album - include Mongoid::Document - embedded_in :band - end - - album = Album.new - album.band = { name: "Death Cab For Cutie" } - p album.band - # => - -See the section on :ref:`Hash Assignment on Embedded Associations ` -for more details - - -Added ``none_of`` Query Method ------------------------------- - -With the addition of ``none_of``, Mongoid 8.1 allows queries to exclude -conditions in bulk. The emitted query will encapsulate the specified -criteria in a ``$nor`` operation. For example: - -.. code:: ruby - - class Building - include Mongoid::Document - field :city, type: String - field :height, type: Integer - field :purpose, type: String - field :occupancy, type: Integer - end - - Building.where(city: 'Portland'). - none_of(:height.lt => 100, - :purpose => 'apartment', - :occupancy.gt => 2500) - -This would query all buildings in Portland, excluding apartments, buildings less than -100 units tall, and buildings with an occupancy greater than 2500 people. - - -Added ``Mongoid::Config.immutable_ids`` ---------------------------------------- - -Coming in Mongoid 9.0, the ``_id`` field will be immutable in both top-level -and embedded documents. This addresses some inconsistency in how mutations -to the ``_id`` field are treated currently. To prepare for this potentially -breaking change, the ``Mongoid::Config.immutable_ids`` flag has been added. It -defaults to ``false``, preserving the existing behavior, but you may set it to -``true`` to prepare your apps for Mongoid 9.0. When this is set to ``true``, -attempts to mutate the ``_id`` of a document will raise an exception. - -.. code:: ruby - - # The default in Mongoid 8.1 - Mongoid::Config.immutable_ids = false - - # The default in Mongoid 9.0 - Mongoid::Config.immutable_ids = true diff --git a/source/release-notes/mongoid-9.0.txt b/source/release-notes/mongoid-9.0.txt deleted file mode 100644 index f6870f8e..00000000 --- a/source/release-notes/mongoid-9.0.txt +++ /dev/null @@ -1,598 +0,0 @@ -.. _mongoid-9.0: - -*********** -Mongoid 9.0 -*********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -This page describes significant changes and improvements in Mongoid 9.0. -The complete list of releases is available `on GitHub -`_ and `in JIRA -`_; -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 -------------------------------------------- - -Mongoid 9 requires Ruby 2.7 or newer or JRuby 9.4. Earlier Ruby and JRuby -versions are not supported. - - -Support for Rails 5 Dropped ------------------------------ - -Mongoid 9 requires Rails 6.0 or newer. Earlier Rails versions are not supported. - - -Deprecated class ``Mongoid::Errors::InvalidStorageParent`` removed ------------------------------------------------------------------- - -The deprecated class ``Mongoid::Errors::InvalidStorageParent`` has been removed. - - -``around_*`` callbacks for embedded documents are now ignored -------------------------------------------------------------- - -Mongoid 8.x and older allows user to define ``around_*`` callbacks for embedded -documents. Starting from 9.0 these callbacks are ignored and will not be executed. -A warning will be printed to the console if such callbacks are defined. - -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. - - -``for_js`` method is deprecated -------------------------------- - -The ``for_js`` method is deprecated and will be removed in Mongoid 10.0. - - -Deprecated options removed --------------------------- - -**Breaking change:** The following config options are removed in Mongoid 9.0. -Please ensure you have removed all references to these from your app. -If you were using ``config.load_defaults 8.1`` prior to upgrading, you will not -experience any behavior change. Refer to earlier release notes for the meaning -of each option. - -- ``:use_activesupport_time_zone`` -- ``:broken_aggregables`` -- ``:broken_alias_handling`` -- ``:broken_and`` -- ``:broken_scoping`` -- ``:broken_updates`` -- ``:compare_time_by_ms`` -- ``:legacy_attributes`` -- ``:legacy_pluck_distinct`` -- ``:legacy_triple_equals`` -- ``:object_id_as_json_oid`` -- ``:overwrite_chained_operators`` - -In addition, support for ``config.load_defaults`` versions 7.5 and -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 ------------------------------------------ - -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]} - -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 # => {} - -Sandbox Mode for Rails Console ------------------------------- - -Mongoid now supports Rails console sandbox mode. If the Rails console started -with ``--sandbox`` flag, Mongoid starts a transaction on the ``:default`` client -before opening the console. This transaction won't be committed; therefore, all -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. - -New Transactions API --------------------- - -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 = Band.create(title: 'Deep Purple') - band.transaction do - band.active = false - band.save! - end - -Please consult :ref:`transactions documentation ` for more details. - -Embedded Documents Always Use Parent Persistence Context --------------------------------------------------------- - -Mongoid 8.x and older allows user to specify persistence context for an -embedded document (using ``store_in`` macro). In Mongoid 9.0 these settings are -ignored for embedded documents; an embedded document now always uses the persistence -context of its parent. - - -Support for Passing Raw Values into Queries -------------------------------------------- - -When performing queries, it is now possible skip Mongoid's type coercion logic -using the ``Mongoid::RawValue`` wrapper class. This can be useful when legacy -data in the database is of a different type than the field definition. - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :age, type: Integer - end - - # Query for the string "42", not the integer 42 - Person.where(age: Mongoid::RawValue("42")) - - -Raise AttributeNotLoaded error when accessing fields omitted from query projection ----------------------------------------------------------------------------------- - -When attempting to access a field on a model instance which was -excluded with the ``.only`` or ``.without`` query projections methods -when the instance was loaded, Mongoid will now raise a -``Mongoid::Errors::AttributeNotLoaded`` error. - -.. code-block:: ruby - - Band.only(:name).first.label - #=> 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 -any Mongoid-specific usages of this class, and change them to -``Mongoid::Errors::AttributeNotLoaded``. Note additionally that -``AttributeNotLoaded`` inherits from ``Mongoid::Errors::MongoidError``, -while ``ActiveModel::MissingAttributeError`` does not. - - -Use configured time zone to typecast Date to Time in queries -------------------------------------------------------------- - -When querying for a Time field using a Date value, Mongoid now correctly -considers ``Time.zone`` to perform type conversion. - -.. code-block:: ruby - - class Magazine - include Mongoid::Document - - field :published_at, type: Time - end - - 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. - -In prior Mongoid versions, the above code would ignore the ``Time.zone`` -(irrespective of the now-removed ``:use_activesupport_time_zone`` -setting) and always using the system time zone to perform the type conversion. - -Note that in prior Mongoid versions, typecasting Date to Time during -persistence operations was already correctly using time zone. - - -```#touch`` method on embedded documents correctly handles ``touch: false`` option ----------------------------------------------------------------------------------- - -When the ``touch: false`` option is set on an ``embedded_in`` relation, -calling the ``#touch`` method on an embedded child document will not -invoke ``#touch`` on its parent document. - -.. code-block:: ruby - - class Address - include Mongoid::Document - include Mongoid::Timestamps - - embedded_in :mall, touch: false - end - - class Mall - include Mongoid::Document - include Mongoid::Timestamps - - embeds_many :addresses - end - - mall = Mall.create! - address = mall.addresses.create! - - 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 -levels of nested embedded documents. - - -``embedded_in`` associations now default to ``touch: true`` ------------------------------------------------------------ - -Updating an embedded subdocument will now automatically touch the parent, -unless you explicitly set ``touch: false`` on the relation: - -.. code-block:: ruby - - class Address - include Mongoid::Document - include Mongoid::Timestamps - - embedded_in :mall, touch: false - end - -For all other associations, the default remains ``touch: false``. - - -Flipped default for ``:replace`` option in ``#upsert`` ------------------------------------------------------- - -Mongoid 8.1 added the ``:replace`` option to the ``#upsert`` method. This -option was used to specify whether or not the existing document should be -updated or replaced. - -Mongoid 9.0 flips the default of this flag from ``true`` => ``false``. - -This means that, by default, Mongoid 9 will update the existing document and -will not replace it. - - -The immutability of the ``_id`` field is now enforced ------------------------------------------------------ - -Prior to Mongoid 9.0, mutating the ``_id`` field behaved inconsistently -depending on whether the document was top-level or embedded, and depending on -how the update was performed. As of 9.0, changing the ``_id`` field will now -raise an exception when the document is saved, if the document had been -previously persisted. - -Mongoid 9.0 also introduces a new feature flag, ``immutable_ids``, which -defaults to ``true``. - -.. code-block:: ruby - - Mongoid::Config.immutable_ids = true - -When set to false, the older, inconsistent behavior is restored. - - -Support Field Aliases on Index Options --------------------------------------- - -Support has been added to use aliased field names in the following options -of the ``index`` macro: ``partial_filter_expression``, ``weights``, -``wildcard_projection``. - -.. code-block:: ruby - - 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. - - -BSON 5 and BSON::Decimal128 Fields ----------------------------------- - -When BSON 4 or earlier is present, any field declared as BSON::Decimal128 will -return a BSON::Decimal128 value. When BSON 5 is present, however, any field -declared as BSON::Decimal128 will return a BigDecimal value by default. - -.. code-block:: ruby - - class Model - include Mongoid::Document - - field :decimal_field, type: BSON::Decimal128 - end - - # under BSON <= 4 - Model.first.decimal_field.class #=> BSON::Decimal128 - - # 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 - - 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. - - -Search Index Management with MongoDB Atlas ------------------------------------------- - -When connected to MongoDB Atlas, Mongoid now supports creating and removing -search indexes. You may do so programmatically, via the Mongoid::SearchIndexable -API: - -.. code-block:: ruby - - class SearchablePerson - include Mongoid::Document - - 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 - - # 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 - -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 -an error. - -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 - - # 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 - - -``Time.configured`` has been removed ------------------------------------- - -``Time.configured`` returned either the time object wrapping the configured -time zone, or the standard Ruby ``Time`` class. This allowed you to query -a time value even if no time zone had been configured. - -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 - - # before: - puts Time.configured.now - - # after: - puts Time.zone.now - - # 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 - - 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 - - $ ruby -ractive_support/values/time_zone \ - -e 'puts ActiveSupport::TimeZone::MAPPING.keys' - - -Records now remember the persistence context in which they were loaded/created ------------------------------------------------------------------------------- - -Consider the following code: - -.. code-block:: ruby - - record = Model.with(collection: 'other_collection') { Model.first } - record.update(field: 'value') - -Prior to Mongoid 9.0, this could would silently fail to execute the update, -because the storage options (here, the specification of an alternate -collection for the model) would not be remembered by the record. Thus, the -record would be loaded from "other_collection", but when updated, would attempt -to look for and update the document in the default collection for Model. To -make this work, you would have had to specify the collection explicitly for -every update. - -As of Mongoid 9.0, records that are created or loaded under explicit storage -options, will remember those options (including a named client, -a different database, or a different collection). - -If you need the legacy (pre-9.0) behavior, you can enable it with the following -flag: - -.. code-block:: ruby - - Mongoid.legacy_persistence_context_behavior = true - -This flag defaults to false in Mongoid 9. - - -Bug Fixes and Improvements --------------------------- - -This section will be for smaller bug fixes and improvements: - -- The ``.unscoped`` method now also clears scopes declared using ``.with_scope`` - `MONGOID-5214 `_. -- When evolving a ``String`` to a ``BigDecimal`` (i.e. when querying a - ``BigDecimal`` field with a ``String`` object), if the - ``map_big_decimal_to_decimal128`` flag set to true, the conversion will - return a ``BSON::Decimal128`` and not a ``String`` - `MONGOID-5484 `_. -- Created new error ``Mongoid::Errors::InvalidEstimatedCountCriteria`` for - when calling ``estimated_document_count`` on a document class with a - default scope - `MONGOID-4960 `_. -- Mongoid now uses primary reads for validations in all cases - `MONGOID-5150 `_. -- Added support for symbol keys in localized field translation hashes - `MONGOID-5334 `_. -- Added index wildcard option - `MONGOID-5388 `_. -- With the ``map_big_decimal_to_decimal128`` flag set to false, ``demongoizing`` - a non-numeric, non-string value that implements ``:to_d`` will return a string - rather than a ``BigDecimal`` - `MONGOID-5507 `_. -- Added support for serializing and deserializing BSON::ObjectId values - when passed as ActiveJob arguments - `MONGOID-5611 `_. diff --git a/source/whats-new.txt b/source/whats-new.txt new file mode 100644 index 00000000..e6b0bbd7 --- /dev/null +++ b/source/whats-new.txt @@ -0,0 +1,567 @@ +.. _mongoid-whats-new: + +========== +What's New +========== + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +Learn what's new in: + +* :ref:`Version 9.0 ` + +To view a list of releases and detailed release notes, see {+odm+} +:github:`Releases ` on GitHub. + +.. _mongoid-version-9.0: + +What's New in 9.0 +----------------- + +The 9.0 release includes the following new features, improvements, and +fixes: + +Railsmdb +~~~~~~~~ + +Alongside the release of {+odm+} v9.0, ``railsmdb``, a command-line utility +for creating, updating, managing, and maintaining Rails applications, is +generally available. + +``railsmdb`` makes it easier to work with MongoDB from the command line +through common generators that {+language+} on Rails developers are already +familiar with. + +For example, you can use ``railsmdb`` to generate stubs for new {+odm+} 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: + +.. code-block:: sh + + bin/railsmdb generate model person name:string birth:date + +.. code-block:: ruby + + class Person + include {+odm+}::Document + include {+odm+}::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:: sh + + bin/railsmdb generate model student --parent=person + +.. code-block:: ruby + + class Student < Person + include {+odm+}::Timestamp + end + +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:: sh + + bin/railsmdb generate model course --collection=classes + +.. code-block:: ruby + + class Course + include {+odm+}::Document + include {+odm+}::Timestamp + store_in collection: 'classes' + end + +For more information see the :github:`mongoid-railsmdb repository +` on GitHub. + +Removal of Support for {+language+} 2.6 and J{+language+} 9.3 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{+odm+} v9.0 requires {+language+} 2.7 or newer or J{+language+} 9.4. +Earlier {+language+} and J{+language+} versions are not supported. + +Removal of Support for Rails 5 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{+odm+} v9.0 requires Rails 6.0 or newer. Earlier Rails versions are not +supported. + +Removal of Deprecated Class ``Mongoid::Errors::InvalidStorageParent`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The deprecated class ``{+odm+}::Errors::InvalidStorageParent`` has been +removed. + +``around_*`` Callbacks for Embedded Documents are Ignored +--------------------------------------------------------- + +{+odm+} v8.x and older allow user to define ``around_*`` callbacks for embedded +documents. Starting in v9.0, these callbacks are ignored and will not be executed. +A warning will be printed to the console if such callbacks are defined. + +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. + +``for_js`` Method is Deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``for_js`` method is deprecated and will be removed in {+odm+} v10.0. + +Removal of Deprecated Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Breaking change:** The following config options are removed in {+odm+} v9.0. +Please ensure you have removed all references to these from your app. +If you were using ``config.load_defaults 8.1`` prior to upgrading, you will not +experience any behavior change. Refer to earlier release notes for the meaning +of each option. + +- ``:use_activesupport_time_zone`` +- ``:broken_aggregables`` +- ``:broken_alias_handling`` +- ``:broken_and`` +- ``:broken_scoping`` +- ``:broken_updates`` +- ``:compare_time_by_ms`` +- ``:legacy_attributes`` +- ``:legacy_pluck_distinct`` +- ``:legacy_triple_equals`` +- ``:object_id_as_json_oid`` +- ``:overwrite_chained_operators`` + +In addition, support for ``config.load_defaults`` versions v7.5 and +prior has been dropped (you must use a minimum of version v8.0.) + +Removal of Deprecated Functionality +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**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 + ``{+odm+}::QueryCache#clear_cache`` should be replaced with + ``Mongo::QueryCache#clear``. All other methods and submodules are + identically named. +- ``Object#blank_criteria?`` method is removed (previously deprecated). +- ``Document#as_json :compact`` option is removed. Please call ```#compact`` on the + returned ``Hash`` object instead. +- The deprecated class ``{+odm+}::Errors::InvalidStorageParent`` has + been removed. + +``touch`` Method Clears Changed State +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In {+odm+} v8.x and older, the ``touch`` method leaves models in the +changed state: + +.. code-block:: ruby + + # v8.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 in v9.0, {+odm+} now correctly clears changed state after using +the ``touch`` method. + +.. code-block:: ruby + + # v9.0 behavior + band = Band.create! + band.touch + band.changed? # => false + band.changes # => {} + +Sandbox Mode for Rails Console +------------------------------ + +{+odm+} now supports Rails console sandbox mode. If the Rails console started +with ``--sandbox`` flag, {+odm+} starts a transaction on the ``:default`` client +before opening the console. This transaction won't be committed. Therefore, all +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. + +New Transactions API +~~~~~~~~~~~~~~~~~~~~ + +{+odm+} 9.0 introduces new transactions API that is inspired by Active Record: + +.. code-block:: ruby + + Band.transaction do + Band.create(title: 'Led Zeppelin') + end + + band = Band.create(title: 'Deep Purple') + band.transaction do + band.active = false + band.save! + end + +To learn more, see the :ref:`mongoid-data-txn` guide. + +Embedded Documents Always Use Parent Persistence Context +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{+odm+} v8.x and older allow user to specify persistence context for an +embedded document (using ``store_in`` macro). In {+odm+} v9.0 these settings are +ignored for embedded documents. An embedded document now always uses the persistence +context of its parent. + +Support for Passing Raw Values into Queries +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When performing queries, it is now possible to skip {+odm+}'s type coercion logic +by using the ``{+odm+}::RawValue`` wrapper class. This can be useful when legacy +data in the database is of a different type than the field definition. + +.. code-block:: ruby + + class Person + include {+odm+}::Document + field :age, type: Integer + end + + # Query for the string "42", not the integer 42 + Person.where(age: {+odm+}::RawValue("42")) + +Raise ``AttributeNotLoaded`` Error When Accessing Fields Omitted from Query Projection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When attempting to access a field on a model instance which was +excluded with the ``only`` or ``without`` query projections methods +when the instance was loaded, {+odm+} will now raise a +``Mongoid::Errors::AttributeNotLoaded`` error. + +.. code-block:: ruby + + Band.only(:name).first.label + #=> raises {+odm+}::Errors::AttributeNotLoaded + + Band.without(:label).first.label = 'Sub Pop Records' + #=> raises {+odm+}::Errors::AttributeNotLoaded + +In earlier {+odm+} versions, the same conditions would raise an +``ActiveModel::MissingAttributeError``. Please check your code for +any {+odm+}-specific usages of this class, and change them to +``Mongoid::Errors::AttributeNotLoaded``. Note additionally that +``AttributeNotLoaded`` inherits from ``Mongoid::Errors::MongoidError``, +while ``ActiveModel::MissingAttributeError`` does not. + +Use Configured Time Zone to Typecast ``Date`` to ``Time`` in Queries +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When querying for a Time field using a Date value, {+odm+} now correctly +considers ``Time.zone`` to perform type conversion. + +.. code-block:: ruby + + class Magazine + include {+odm+}::Document + + field :published_at, type: Time + end + + 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. + +In prior {+odm+} versions, the above code would ignore the ``Time.zone`` +(irrespective of the now-removed ``:use_activesupport_time_zone`` +setting) and would always use the system time zone to perform the type +conversion. + +Note that in prior {+odm+} versions, typecasting ``Date`` to ``Time`` +during persistence operations was already correctly using time zone. + +``touch`` Method on Embedded Documents Correctly Handles ``touch: false`` Option +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When the ``touch: false`` option is set on an ``embedded_in`` relation, +calling the ``touch`` method on an embedded child document will not +invoke ``touch`` on its parent document. + +.. code-block:: ruby + + class Address + include {+odm+}::Document + include {+odm+}::Timestamps + + embedded_in :mall, touch: false + end + + class Mall + include {+odm+}::Document + include {+odm+}::Timestamps + + embeds_many :addresses + end + + mall = Mall.create! + address = mall.addresses.create! + + 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 +levels of nested embedded documents. + +``embedded_in`` Associations Default to ``touch: true`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Updating an embedded sub-document will now automatically touch the +parent, unless you explicitly set ``touch: false`` on the relation: + +.. code-block:: ruby + + class Address + include {+odm+}::Document + include {+odm+}::Timestamps + + embedded_in :mall, touch: false + end + +For all other associations, the default remains ``touch: false``. + +Flipped Default for ``:replace`` Option in ``upsert`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{+odm+} v8.1 added the ``:replace`` option to the ``upsert`` method. This +option was used to specify whether or not the existing document should be +updated or replaced. + +{+odm+} v9.0 flips the default of this flag from ``true`` to ``false``. +This means that, by default, {+odm+} v9.0 updates the existing document and +does not replace it. + +Immutability of the ``_id`` Field Enforced +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before {+odm+} v9.0, mutating the ``_id`` field behaved inconsistently +depending on whether the document was top-level or embedded, and depending on +how the update was performed. In v9.0, changing the ``_id`` field will now +raise an exception when the document is saved, if the document had been +previously persisted. + +{+odm+} 9.0 also introduces a new feature flag, ``immutable_ids``, which +defaults to ``true``. + +.. code-block:: ruby + + {+odm+}::Config.immutable_ids = true + +When set to ``false``, the older, inconsistent behavior is restored. + +Support for Field Aliases on Index Options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support has been added to use aliased field names in the following options +of the ``index`` macro: ``partial_filter_expression``, ``weights``, +``wildcard_projection``. + +.. code-block:: ruby + + class Person + include {+odm+}::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 {+odm+}'s functionality may not support such changes. + +BSON 5 and ``BSON::Decimal128`` Fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When BSON 4 or earlier is present, any field declared as ``BSON::Decimal128`` will +return a ``BSON::Decimal128`` value. When BSON 5 is present, however, any field +declared as ``BSON::Decimal128`` will return a ``BigDecimal`` value by default. + +.. code-block:: ruby + + class Model + include {+odm+}::Document + + field :decimal_field, type: BSON::Decimal128 + end + + # under BSON <= 4 + Model.first.decimal_field.class #=> BSON::Decimal128 + + # under BSON >= 5 + Model.first.decimal_field.class #=> BigDecimal + +If you need literal ``BSON::Decimal128`` values with BSON 5, you may instruct +{+odm+} to allow literal ``BSON::Decimal128`` fields: + +.. code-block:: ruby + + Model.first.decimal_field.class #=> BigDecimal + + {+odm+}.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. + +Search Index Management for MongoDB Atlas +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When connected to MongoDB Atlas, {+odm+} now supports creating and removing +search indexes. You may do so programmatically, via the ``Mongoid::SearchIndexable`` +API: + +.. code-block:: ruby + + class SearchablePerson + include {+odm+}::Document + + 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 + + # 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 + +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 +an error. + +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 + + # 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 + +Removal of ``Time.configured`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Time.configured`` returned either the time object wrapping the configured +time zone, or the standard {+language+} ``Time`` class. This allowed you to query +a time value even if no time zone had been configured. + +{+odm+} 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 + + # before: + puts Time.configured.now + + # after: + puts Time.zone.now + + # 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 + + 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 + + $ ruby -ractive_support/values/time_zone \ + -e 'puts ActiveSupport::TimeZone::MAPPING.keys' + +Records Remember Persistence Context of Creation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Consider the following code: + +.. code-block:: ruby + + record = Model.with(collection: 'other_collection') { Model.first } + record.update(field: 'value') + +Prior to {+odm+} v9.0, this could would silently fail to execute the update, +because the storage options (here, the specification of an alternate +collection for the model) would not be remembered by the record. Thus, the +record would be loaded from ``other_collection``, but when updated, would attempt +to look for and update the document in the default collection for Model. To +make this work, you would have had to specify the collection explicitly for +every update. + +As of {+odm+} v9.0, records that are created or loaded under explicit storage +options, will remember those options (including a named client, +a different database, or a different collection). + +If you need the legacy (pre-v9.0) behavior, you can enable it with the following +flag: + +.. code-block:: ruby + + Mongoid.legacy_persistence_context_behavior = true + +This flag defaults to false in {+odm+} v9.0. From c07d7b4334ab229d78acdd2293ec87134b65c5fa Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:33:14 -0500 Subject: [PATCH 103/113] DOCSP-42773: api links (#88) * DOCSP-42773: api links * fix * link fixes --- snooty.toml | 5 ++--- source/aggregation.txt | 6 +++--- source/api.txt | 15 +++++++++++++ source/configuration/sharding.txt | 2 +- source/data-modeling/documents.txt | 2 +- source/data-modeling/indexes.txt | 6 +++--- .../persistence-configuration.txt | 20 +++++++++--------- source/img/rails-blog-new-comment.png | Bin 27391 -> 0 bytes source/img/rails-new-blog.png | Bin 19612 -> 0 bytes source/index.txt | 2 +- 10 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 source/api.txt delete mode 100644 source/img/rails-blog-new-comment.png delete mode 100644 source/img/rails-new-blog.png diff --git a/snooty.toml b/snooty.toml index b9bbb429..f2840471 100644 --- a/snooty.toml +++ b/snooty.toml @@ -27,7 +27,6 @@ quickstart-sinatra-app-name = "my-sinatra-app" quickstart-rails-app-name = "my-rails-app" feedback-widget-title = "Feedback" server-manual = "Server manual" -api-root = "https://www.mongodb.com/docs/mongoid/master/api/Mongoid" -api = "https://www.mongodb.com/docs/mongoid/master/api" -ruby-api = "https://www.mongodb.com/docs/ruby-driver/current/api/Mongo" +api = "https://www.mongodb.com/docs/mongoid/current/api" +ruby-api = "https://www.mongodb.com/docs/ruby-driver/current/api" active-record-docs = "https://guides.rubyonrails.org" diff --git a/source/aggregation.txt b/source/aggregation.txt index 55fc3a16..a30317f2 100644 --- a/source/aggregation.txt +++ b/source/aggregation.txt @@ -227,6 +227,6 @@ API Documentation To learn more about any of the methods discussed in this guide, see the following API documentation: -- `group() <{+api-root+}/Criteria/Queryable/Aggregable.html#group-instance_method>`__ -- `project() <{+api-root+}/Criteria/Queryable/Aggregable.html#project-instance_method>`__ -- `unwind() <{+api-root+}/Criteria/Queryable/Aggregable.html#unwind-instance_method>`__ \ No newline at end of file +- `group() <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#group-instance_method>`__ +- `project() <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#project-instance_method>`__ +- `unwind() <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#unwind-instance_method>`__ \ No newline at end of file diff --git a/source/api.txt b/source/api.txt new file mode 100644 index 00000000..eb027514 --- /dev/null +++ b/source/api.txt @@ -0,0 +1,15 @@ +.. _mongoid-api-landing: + +================= +API Documentation +================= + +.. meta:: + :description: Read the API documentation for Mongoid and the Ruby Driver. + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + {+odm+} <{+api+}> + Ruby Driver <{+ruby-api+}> diff --git a/source/configuration/sharding.txt b/source/configuration/sharding.txt index 52d77a28..95c259a0 100644 --- a/source/configuration/sharding.txt +++ b/source/configuration/sharding.txt @@ -165,5 +165,5 @@ API Documentation To learn more about the ``shard_key`` macro discussed in this guide, see the `shard_key -<{+api-root+}/Shardable/ClassMethods.html#shard_key-instance_method>`__ API +<{+api+}/Mongoid/Shardable/ClassMethods.html#shard_key-instance_method>`__ API documentation. diff --git a/source/data-modeling/documents.txt b/source/data-modeling/documents.txt index 5fcbea93..97442968 100644 --- a/source/data-modeling/documents.txt +++ b/source/data-modeling/documents.txt @@ -44,7 +44,7 @@ in a sample ``Person`` model class: end You can find more information about the ``Document`` module in the `API -documentation <{+api-root+}/Document.html>`__. +documentation <{+api+}/Mongoid/Document.html>`__. Work with Documents ------------------- diff --git a/source/data-modeling/indexes.txt b/source/data-modeling/indexes.txt index 6e8dcbd6..736e0141 100644 --- a/source/data-modeling/indexes.txt +++ b/source/data-modeling/indexes.txt @@ -208,12 +208,12 @@ API Documentation ----------------- To learn more about using indexes in {+odm+}, see the -`Mongoid::Indexable::ClassMethods <{+api-root+}/Indexable/ClassMethods.html>`__ +`Mongoid::Indexable::ClassMethods <{+api+}/Mongoid/Indexable/ClassMethods.html>`__ documentation. To learn more about index options, see the `Mongoid::Indexable::Validators::Options -<{+api-root+}/Indexable/Validators/Options.html>`__ documentation. +<{+api+}/Mongoid/Indexable/Validators/Options.html>`__ documentation. To learn more about using Atlas Search indexes in {+odm+}, see the -`Mongoid::SearchIndexable::ClassMethods <{+api-root+}/SearchIndexable/ClassMethods.html>`__ +`Mongoid::SearchIndexable::ClassMethods <{+api+}/Mongoid/SearchIndexable/ClassMethods.html>`__ documentation. \ No newline at end of file diff --git a/source/data-modeling/persistence-configuration.txt b/source/data-modeling/persistence-configuration.txt index b281b5d5..83a9f032 100644 --- a/source/data-modeling/persistence-configuration.txt +++ b/source/data-modeling/persistence-configuration.txt @@ -281,13 +281,13 @@ API Documentation For more information about the methods mentioned in this guide, see the following API documentation: -- `#client_name <{+api-root+}/PersistenceContext.html#client_name-instance_method>`__ -- `#database_name <{+api-root+}/Clients/Options/ClassMethods.html#database_name-instance_method>`__ -- `#collection_name <{+api-root+}/Clients/Options/ClassMethods.html#collection_name-instance_method>`__ -- `#store_in <{+api-root+}/Clients/StorageOptions/ClassMethods.html#store_in-instance_method>`__ -- `Model.with <{+api-root+}/Clients/Options.html#with-instance_method>`__ -- `Mongoid::PersistenceContext <{+api-root+}/PersistenceContext.html>`__ -- `Mongoid.override_client <{+api-root+}/Config.html#override_client-instance_method>`__ -- `Mongoid.override_database <{+api-root+}/Config.html#override_database-instance_method>`__ -- `Model.mongo_client <{+api-root+}/Clients/Options/ClassMethods.html#mongo_client-instance_method>`__ -- `Model.collection <{+api-root+}/Clients/Options/ClassMethods.html#collection-instance_method>`__ \ No newline at end of file +- `#client_name <{+api+}/Mongoid/PersistenceContext.html#client_name-instance_method>`__ +- `#database_name <{+api+}/Mongoid/Clients/Options/ClassMethods.html#database_name-instance_method>`__ +- `#collection_name <{+api+}/Mongoid/Clients/Options/ClassMethods.html#collection_name-instance_method>`__ +- `#store_in <{+api+}/Mongoid/Clients/StorageOptions/ClassMethods.html#store_in-instance_method>`__ +- `Model.with <{+api+}/Mongoid/Clients/Options.html#with-instance_method>`__ +- `Mongoid::PersistenceContext <{+api+}/Mongoid/PersistenceContext.html>`__ +- `Mongoid.override_client <{+api+}/Mongoid/Config.html#override_client-instance_method>`__ +- `Mongoid.override_database <{+api+}/Mongoid/Config.html#override_database-instance_method>`__ +- `Model.mongo_client <{+api+}/Mongoid/Clients/Options/ClassMethods.html#mongo_client-instance_method>`__ +- `Model.collection <{+api+}/Mongoid/Clients/Options/ClassMethods.html#collection-instance_method>`__ \ No newline at end of file diff --git a/source/img/rails-blog-new-comment.png b/source/img/rails-blog-new-comment.png deleted file mode 100644 index f82a56e9f6b86f7fdcb9a2fb7ab4c0085ec13a90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27391 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 diff --git a/source/index.txt b/source/index.txt index 4077ab12..5ec4d9ba 100644 --- a/source/index.txt +++ b/source/index.txt @@ -19,7 +19,7 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Model Your Data Configuration /working-with-data - API + API Documentation /whats-new Issues & Help /additional-resources From 67ae702a947f83f352d404c877f26dd2ecc79ccd Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:15:37 -0500 Subject: [PATCH 104/113] DOCSP-42772: compatibility (#90) * DOCSP-42772: compatibility * small fix * small fix * SA PR fixes 1 * delete files for old build system * column width adjustment --- .gitignore | 4 - README.rst | 128 +---- conf.py | 115 ---- config/build_conf.yaml | 41 -- config/htaccess.yaml | 31 -- config/integration.yaml | 10 - config/push.yaml | 2 - config/sphinx.yaml | 31 -- config/sphinx_local.yaml | 18 - snooty.toml | 4 + source/.gitignore | 1 - source/add-existing.txt | 2 +- source/additional-resources.txt | 8 +- source/compatibility.txt | 108 ++++ source/ecosystem.txt | 2 +- .../language-compatibility-table-mongoid.rst | 48 ++ .../mongodb-compatibility-table-mongoid.rst | 34 ++ .../rails-compatibility-table-mongoid.rst | 52 ++ .../ror-compatibility-table-mongoid.rst | 70 +++ ...uby-driver-compatibility-table-mongoid.rst | 14 + source/index.txt | 5 +- source/installation-configuration.txt | 25 - source/installation.txt | 66 --- source/legacy-files/upgrading.txt | 2 +- source/nesting-levels.txt | 20 - source/quick-start-rails.txt | 8 +- .../download-and-install.txt | 2 +- source/quick-start-rails/next-steps.txt | 3 +- source/quick-start-sinatra.txt | 2 +- source/reference/compatibility.txt | 507 ------------------ source/reference/configuration.txt | 18 +- source/reference/rails-integration.txt | 2 +- source/release-notes.txt | 31 -- source/whats-new.txt | 2 +- source/working-with-data.txt | 37 -- 35 files changed, 366 insertions(+), 1087 deletions(-) delete mode 100644 .gitignore delete mode 100644 conf.py delete mode 100644 config/build_conf.yaml delete mode 100644 config/htaccess.yaml delete mode 100644 config/integration.yaml delete mode 100644 config/push.yaml delete mode 100644 config/sphinx.yaml delete mode 100644 config/sphinx_local.yaml delete mode 100644 source/.gitignore create mode 100644 source/compatibility.txt create mode 100644 source/includes/language-compatibility-table-mongoid.rst create mode 100644 source/includes/mongodb-compatibility-table-mongoid.rst create mode 100644 source/includes/rails-compatibility-table-mongoid.rst create mode 100644 source/includes/ror-compatibility-table-mongoid.rst create mode 100644 source/includes/ruby-driver-compatibility-table-mongoid.rst delete mode 100644 source/installation-configuration.txt delete mode 100644 source/installation.txt delete mode 100644 source/nesting-levels.txt delete mode 100644 source/reference/compatibility.txt delete mode 100644 source/release-notes.txt delete mode 100644 source/working-with-data.txt diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 2e79a6f4..00000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.DS_Store -giza.log -build/ -.yardoc diff --git a/README.rst b/README.rst index d719f98c..d58e8179 100644 --- a/README.rst +++ b/README.rst @@ -1,135 +1,23 @@ -===================== -Mongoid Documentation -===================== +============================= +MongoDB Mongoid Documentation +============================= -This repository contains documentation for Mongoid, the object-document -mapper for MongoDB in Ruby. - -Build Locally -------------- - -To build the documentation locally, - -- Install `giza `_, if not already - installed. Note that giza requires Python 2.7 and does not work with Python 3. - There is an `installation guide - `_ on the MongoDB meta site - to help you get started. - -- Run the following to download and build this documentation locally:: - - git clone git@github.com:mongodb/docs-mongoid - cd docs-mongoid/ - make html - -The generated HTML will be placed in `build/master/html/`. - -*Note*: The build process invokes Sphinx and expects Sphinx's various -binaries to be in PATH. - -Stage ------ - -Install `mut `_ following its installation -instructions. Note that mut requires Python 3 and does not work with Python 2.7. - -Request access to the documentation staging bucket. Obtain AWS S3 -access key id and secret access key. - -Create ``~/.config/giza-aws-authentication.conf`` with the following contents:: - - [authentication] - accesskey= - secretkey= - -(If you run ``make stage`` without configuring authentication, it will -also give you these instructions.) - -Finally to publish the docs to the staging bucket, run:: - - make stage - -The output will include a URL to the published documentation. - -Deploy To Live Site -------------------- - -Install `mut `_ following its installation -instructions. Note that mut requires Python 3 and does not work with Python 2.7. - -Request access to the production documentation bucket. Obtain AWS S3 -access key id and secret access key. - -Create ``~/.config/giza-aws-authentication.conf`` with the following contents:: - - [authentication] - accesskey= - secretkey= - -Publish the docs to the production bucket:: - - make publish deploy - -Using Docker ------------- - -Build the docker image:: - - docker build -t docs-mongoid . - -Build the documentation to ``build`` subdir, clobbering its existing contents:: - - rm -rf build && \ - mkdir build && \ - docker run -itv `pwd`/build:/build \ - docs-mongoid sh -c 'make html && rsync -av build/ /build' - -Note that since Docker container runs everything as root, the built files -will also be owned by root and will require root access to modify or remove. - -Deploy to live site (no need to configure ``~/.config/giza-aws-authentication.conf`` -on the host system):: - - docker run -it \ - -e AWS_ACCESS_KEY_ID= \ - -e AWS_SECRET_ACCESS_KEY= \ - docs-mongoid make publish deploy - -Contribute ----------- - -To contribute to the documentation, please sign the `MongoDB -Contributor Agreement -`_ if you have not -already. - -See the following documents that provide an overview of the -documentation style and process: - -- `Style Guide `_ -- `Documentation Practices `_ -- `Documentation Organization `_ -- `Build Instructions `_ +This repository contains documentation for the MongoDB Mongoid ODM for Ruby. File JIRA Tickets ----------------- -File issue reports or requests at the `Documentation Jira Project -`_. - -Listed Mongoid Branches ------------------------ - -The branch list as well as which is the current one is maintained here: https://github.com/mongodb/docs-tools/blob/master/data/mongoid-published-branches.yaml +Please file issue reports or requests at the `Documentation Jira Project +`__. Licenses -------- All documentation is available under the terms of a `Creative Commons -License `_. +License `__. The MongoDB Documentation Project is governed by the terms of the `MongoDB Contributor Agreement -`_. +`__. -- The MongoDB Docs Team diff --git a/conf.py b/conf.py deleted file mode 100644 index 04b71b94..00000000 --- a/conf.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -# -# MongoDB documentation build configuration file, created by -# sphinx-quickstart on Mon Oct 3 09:58:40 2011. -# -# This file is execfile()d with the current directory set to its containing dir. - -import sys -import os.path -import datetime - -from sphinx.errors import SphinxError - -from giza.config.runtime import RuntimeStateConfig -from giza.config.helper import fetch_config, get_versions, get_manual_path - -conf = fetch_config(RuntimeStateConfig()) -sconf = conf.system.files.data.sphinx_local - -sys.path.append(os.path.join(conf.paths.projectroot, conf.paths.buildsystem, 'sphinxext')) - - -# -- General configuration ---------------------------------------------------- - -needs_sphinx = '1.0' - -extensions = [ - 'sphinx.ext.extlinks', - 'sphinx.ext.todo', - 'mongodb', - 'directives', - 'intermanual', - 'fasthtml' -] - -locale_dirs = [ os.path.join(conf.paths.projectroot, conf.paths.locale) ] -gettext_compact = False - -templates_path = ['.templates'] -exclude_patterns = [] - -source_suffix = '.txt' - -master_doc = sconf.master_doc -language = 'en' -project = sconf.project - -copyright = u'2008-{0}'.format(datetime.date.today().year) - -version = conf.version.branch -release = conf.version.release - - -rst_epilog = '\n'.join([ - '.. |branch| replace:: ``{0}``'.format(conf.git.branches.current), - '.. |copy| unicode:: U+000A9', - '.. |year| replace:: {0}'.format(datetime.date.today().year), - '.. |hardlink| replace:: http://docs.mongodb.com/mongoid', -]) - -pygments_style = 'sphinx' - -extlinks = { - 'manual': ('https://www.mongodb.com/docs/manual%s', ''), -} - -## add `extlinks` for each published version. -for i in conf.git.branches.published: - extlinks[i] = ( ''.join([ conf.project.url, '/', i, '%s' ]), '' ) - -intersphinx_mapping = {} - -intersphinx_mapping = {} -for i in conf.system.files.data.intersphinx: - intersphinx_mapping[i.name] = ( i.url, os.path.join(conf.paths.projectroot, - conf.paths.output, - i.path)) - -languages = [] -# -- Options for HTML output --------------------------------------------------- - -html_theme = sconf.theme.name -html_theme_path = [ os.path.join(conf.paths.buildsystem, 'themes') ] -html_title = conf.project.title -htmlhelp_basename = 'MongoDB' - -html_logo = ".static/logo-mongodb.png" - -html_copy_source = False -html_domain_indices = True -html_use_index = False -html_split_index = False -html_show_sourcelink = False -html_show_sphinx = True -html_show_copyright = True - - -html_theme_options = { - 'branch': conf.git.branches.current, - 'manual_path': get_manual_path(conf), - 'translations': languages, - 'language': language, - 'repo_name': sconf.theme.repo, - 'jira_project': sconf.theme.jira, - 'google_analytics': sconf.theme.google_analytics, - 'project': sconf.theme.project, - 'version': version, - 'version_selector': get_versions(conf), - 'active_branches': conf.version.active, - 'stable': conf.version.stable, - 'sitename': sconf.theme.sitename, - 'nav_excluded': sconf.theme.nav_excluded, -} - -html_sidebars = sconf.sidebars diff --git a/config/build_conf.yaml b/config/build_conf.yaml deleted file mode 100644 index c6673055..00000000 --- a/config/build_conf.yaml +++ /dev/null @@ -1,41 +0,0 @@ -git: - repo: 'mongodb/docs-mongoid' - remote: - upstream: 'mongodb/docs-mongoid' - tools: 'mongodb/docs-tools' -project: - name: 'mongoid' - tag: 'mongoid' - url: 'https://docs.mongodb.com/mongoid' - title: 'Mongoid Manual' - branched: true -version: - release: 'upcoming' - branch: 'master' -system: - runstate: - serial_sphinx: publish - files: - - 'intersphinx.yaml' - - 'integration.yaml' - - 'sphinx_local.yaml' - - 'push.yaml' - - htaccess: ['htaccess.yaml'] -assets: - - branch: master - path: build/docs-tools - repository: https://github.com/mongodb/docs-tools.git - - branch: master - path: build/mongoid-master - repository: https://github.com/mongodb/mongoid.git -paths: - output: 'build' - source: 'source-master' - includes: 'source-master/includes' - images: 'source-master/images' - tools: 'bin' - buildsystem: 'build/docs-tools' - builddata: 'config' - locale: 'locale' - -... \ No newline at end of file diff --git a/config/htaccess.yaml b/config/htaccess.yaml deleted file mode 100644 index b6cb9da8..00000000 --- a/config/htaccess.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Each YAML document has the following schema: -# -# from: "the path to the redirected URL" -# to: "the path to the new location for the resource" -# type: "currently this script only supports 'redirect', and -# ignores all documents with another type." -# code: "the HTTP redirect code. Raises exception if not 301, 302, 303." -# outputs: "a list of branches/paths as follows:" -# -# - 'all': generates redirects for all published branches, but *not* 'manual'. But giza will take care of manual as part of integration.yaml logic in redirects.py -# -# - '': generates redirects for a specific branch. -# -# - 'before-': generates redirects for all branches that -# reflect releases previous to the specified branch. (Inclusive.) -# -# - 'after-': generates redirects for all branches that -# reflect releases after the specified branch. (Non-inclusive.) -# -# All paths are relative to the ``source/`` directory. -# -######################################################################## -### Giza requires this file, even if no redirects -### More as a safety -- in case, we remove redirects during deploys -from: '/' -to: '/master' -type: 'redirect' -code: 301 -outputs: - - {'/mongoid': 'https://docs.mongodb.com/mongoid'} -... diff --git a/config/integration.yaml b/config/integration.yaml deleted file mode 100644 index 406866dd..00000000 --- a/config/integration.yaml +++ /dev/null @@ -1,10 +0,0 @@ -base: - links: - - { '6.2': 'master' } - targets: - - 'html' - - 'dirhtml' - doc-root: - - 'sitemap.xml' - - '.htaccess' - branch-root: [] diff --git a/config/push.yaml b/config/push.yaml deleted file mode 100644 index a3795069..00000000 --- a/config/push.yaml +++ /dev/null @@ -1,2 +0,0 @@ -### Apparently, giza requires this file, even if empty -### Must include ref to this file in the build_conf.yaml diff --git a/config/sphinx.yaml b/config/sphinx.yaml deleted file mode 100644 index 9272a19b..00000000 --- a/config/sphinx.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# The items in the ``builders`` list are the name of Sphinx builders -# supported by ``sphinx-build``. -# -# The ``prerequsites`` list stores all targets that must build before -# sphinx can begin. -# -# The ``generated-source`` list stores all the targets that generate rst. - - -prerequisites: - - setup - - intersphinx - - generate-source -generated-source: - - tables - - api - - toc - - intersphinx - - images -### --- #### -web-base: - tags: - - web -### --- #### -dirhtml: - builder: dirhtml - inherit: web-base -html: - builder: html - inherit: web-base -... \ No newline at end of file diff --git a/config/sphinx_local.yaml b/config/sphinx_local.yaml deleted file mode 100644 index 3bd81124..00000000 --- a/config/sphinx_local.yaml +++ /dev/null @@ -1,18 +0,0 @@ -project: 'mongoid-manual' -master_doc: 'index' -logo: ".static/logo.png" -paths: - static: ['source/.static'] -theme: - name: 'mongoid' - project: 'mongoid' - google_analytics: 'UA-7301842-8' - book_path_base: 'mongoid' - repo: 'docs-mongoid' - jira: 'DOCS' - sitename: 'Mongoid' - nav_excluded: [] -sidebars: - '**': - - 'pagenav.html' -... diff --git a/snooty.toml b/snooty.toml index f2840471..6d6f5a8f 100644 --- a/snooty.toml +++ b/snooty.toml @@ -3,9 +3,12 @@ title = "Mongoid" intersphinx = [ "https://www.mongodb.com/docs/manual/objects.inv", + "https://www.mongodb.com/docs/drivers/objects.inv", "https://www.mongodb.com/docs/atlas/objects.inv", ] +sharedinclude_root = "https://raw.githubusercontent.com/10gen/docs-shared/main/" + toc_landing_pages = [ "/quick-start-rails", "/quick-start-sinatra", @@ -23,6 +26,7 @@ version = "9.0" full-version = "{+version+}.4" ruby-driver = "Ruby driver" language = "Ruby" +ror = "{+language+} on Rails" quickstart-sinatra-app-name = "my-sinatra-app" quickstart-rails-app-name = "my-rails-app" feedback-widget-title = "Feedback" diff --git a/source/.gitignore b/source/.gitignore deleted file mode 100644 index e35d8850..00000000 --- a/source/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_build diff --git a/source/add-existing.txt b/source/add-existing.txt index fdd6fde2..6fa94c9d 100644 --- a/source/add-existing.txt +++ b/source/add-existing.txt @@ -21,7 +21,7 @@ Overview -------- In this guide, you can learn how to add {+odm+} to an existing Sinatra -or Ruby on Rails (Rails) application. To learn how to set up a new +or {+ror+} (Rails) application. To learn how to set up a new application that uses {+odm+}, see one of the following guides: - :ref:`mongoid-quick-start-rails` diff --git a/source/additional-resources.txt b/source/additional-resources.txt index 8de82da5..065b8ee2 100644 --- a/source/additional-resources.txt +++ b/source/additional-resources.txt @@ -26,10 +26,10 @@ Screencasts of setting up an app, querying for documents, adding embedded associations, overriding the id, and more. -- `Ruby on Rails Web Services and Integration with MongoDB, Week 3: Mongoid +- `{+ror+} Web Services and Integration with MongoDB, Week 3: Mongoid `_ - A detailed introduction to Mongoid and Ruby on Rails web services. + A detailed introduction to Mongoid and {+ror+} web services. - `Create a search bar in Rails with Mongoid `_ @@ -48,9 +48,9 @@ Articles Creating a Sinatra API with Mongoid. -- `Converting an existing Ruby on Rails application to MongoDB `_ +- `Converting an existing {+ror+} application to MongoDB `_ - How to Convert an existing Ruby on Rails application to use MongoDB and Mongoid. + How to Convert an existing {+ror+} application to use MongoDB and Mongoid. Sample Applications diff --git a/source/compatibility.txt b/source/compatibility.txt new file mode 100644 index 00000000..d44c016b --- /dev/null +++ b/source/compatibility.txt @@ -0,0 +1,108 @@ +.. _mongoid-compatibility: + +============= +Compatibility +============= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: backwards compatibility, versions, upgrade + +{+language+} Driver Compatibility +------------------------- + +The following compatibility table specifies the versions of the +:ruby:`{+ruby-driver+} ` that are compatible with each +{+odm+} version. + +.. note:: Patch Version Compatibility + + Older patch versions of {+odm+} within the same minor release might + support older {+ruby-driver+} versions. For example, {+odm+} v7.0.5 + supports {+ruby-driver+} v2.5 and later, but {+odm+} v7.0.6 requires + driver v2.7 or later. + +.. include:: /includes/ruby-driver-compatibility-table-mongoid.rst + +MongoDB Compatibility +--------------------- + +The following compatibility table specifies the recommended version or versions +of {+odm+} that you can use with a specific version of MongoDB. To use +features of a particular MongoDB Server version, both the +{+ruby-driver+} and {+odm+} must be compatible with that MongoDB +version. To learn about the driver's MongoDB compatibility details, +see :ruby:`Compatibility ` +in the {+ruby-driver+} documentation. + +The first column lists the version of {+odm+}. + +.. sharedinclude:: dbx/lifecycle-schedule-callout.rst + +.. sharedinclude:: dbx/compatibility-table-legend.rst + +.. include:: /includes/mongodb-compatibility-table-mongoid.rst + +To learn more about how to read the compatibility tables, see the guide +on :ref:`MongoDB Compatibility Tables `. + +Language Compatibility +---------------------- + +The following compatibility table specifies the recommended version or +versions of {+odm+} that you can use with specific {+language+} +interpreter versions. + +The first column lists the version of {+odm+}. + +.. include:: /includes/language-compatibility-table-mongoid.rst + +To learn more about how to read the compatibility tables, see the guide +on :ref:`MongoDB Compatibility Tables `. + +{+ror+} Compatibility +--------------------------- + +The following compatibility table specifies which versions of {+ror+} +are supported by {+odm+}. + +.. include:: /includes/rails-compatibility-table-mongoid.rst + +Rails Frameworks Support +~~~~~~~~~~~~~~~~~~~~~~~~ + +{+odm+} is compatible with many of the frameworks that comprise {+ror+}. +In this section, you can learn about which frameworks you can use with {+odm+}. + +{+odm+} attempts to offer API compatibility with `Active Record +<{+active-record-docs+}/active_record_basics.html>`__, but libraries +that depend directly on Active Record might not work as expected if you +use {+odm+} as a direct replacement. + +.. note:: + + You can use {+odm+} alongside Active Record within the same + application. + +.. include:: /includes/ror-compatibility-table-mongoid.rst + +How to Get Help +--------------- + +If you have questions about compatibility, visit the following resources +for further guidance: + +- Ask questions on our :community-forum:`MongoDB Community Forums <>`. +- Visit the :technical-support:`Support Channels `. +- File an issue or feature request in Jira, our issue tracker. You can + find instructions on filing a ticket on the + :ref:`mongoid-issues-and-help` page. diff --git a/source/ecosystem.txt b/source/ecosystem.txt index a754c45e..9d89cc70 100644 --- a/source/ecosystem.txt +++ b/source/ecosystem.txt @@ -19,7 +19,7 @@ Projects - `Workarea Commerce `_ - Workarea is an enterprise-grade Ruby on Rails commerce platform that uses Mongoid. + Workarea is an enterprise-grade {+ror+} commerce platform that uses Mongoid. Extension Libraries diff --git a/source/includes/language-compatibility-table-mongoid.rst b/source/includes/language-compatibility-table-mongoid.rst new file mode 100644 index 00000000..e7d3b66a --- /dev/null +++ b/source/includes/language-compatibility-table-mongoid.rst @@ -0,0 +1,48 @@ +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :class: compatibility-large + + * - {+odm+} Version + - {+language+} 3.2 + - {+language+} 3.1 + - {+language+} 3.0 + - {+language+} 2.7 + - {+language+} 2.6 + - {+language+} 2.5 + - J{+language+} 9.4 + - J{+language+} 9.3 + - J{+language+} 9.2 + + * - 9.0 + - ✓ + - ✓ + - ✓ + - ✓ + - + - + - ✓ + - + - + + * - 8.1 + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - + - + - ✓ + - + + * - 8.0 + - + - ✓ + - ✓ + - ✓ + - ✓ + - + - + - ✓ + - diff --git a/source/includes/mongodb-compatibility-table-mongoid.rst b/source/includes/mongodb-compatibility-table-mongoid.rst new file mode 100644 index 00000000..06fbb6af --- /dev/null +++ b/source/includes/mongodb-compatibility-table-mongoid.rst @@ -0,0 +1,34 @@ +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :class: compatibility-large + + * - {+odm+} Version + - MongoDB 8.0 + - MongoDB 7.0 + - MongoDB 6.0 + - MongoDB 5.0 + - MongoDB 4.4 + - MongoDB 4.2 + - MongoDB 4.0 + - MongoDB 3.6 + + * - 9.0 + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + + * - 8.0 to 8.1 + - + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ + - ✓ diff --git a/source/includes/rails-compatibility-table-mongoid.rst b/source/includes/rails-compatibility-table-mongoid.rst new file mode 100644 index 00000000..253cd840 --- /dev/null +++ b/source/includes/rails-compatibility-table-mongoid.rst @@ -0,0 +1,52 @@ +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :class: compatibility-large no-padding + + * - {+odm+} Version + - Rails 8.0 + - Rails 7.2 + - Rails 7.1 + - Rails 7.0 + - Rails 6.1 + - Rails 6.0 + - Rails 5.2 + - Rails 5.1 + + * - 9.0 + - ✓ [#rails-8.0]_ + - ✓ [#rails-7.2]_ + - ✓ [#rails-7.1]_ + - ✓ + - ✓ + - ✓ + - + - + + * - 8.1 + - ✓ [#rails-8.0]_ + - ✓ [#rails-7.2]_ + - ✓ [#rails-7.1]_ + - ✓ + - ✓ + - ✓ + - ✓ [#rails-5-ruby-3.0]_ + - + + * - 8.0 + - + - + - ✓ [#rails-7.1]_ + - ✓ + - ✓ + - ✓ + - ✓ [#rails-5-ruby-3.0]_ + - + +.. [#rails-8.0] Rails 8.0 requires {+odm+} v8.1.7 and v9.0.3 in the respective 8.1 and 9.0 stable branches. + +.. [#rails-7.2] Rails 7.2 requires {+odm+} v8.1.6 and v9.0.2 in the respective 8.1 and 9.0 stable branches. + +.. [#rails-7.1] Rails 7.1 requires {+odm+} v8.0.7 or v8.1.3 in the respective 8.0 and 8.1 stable branches. + +.. [#rails-5-ruby-3.0] Using Rails 5.x with Ruby 3 is not supported. diff --git a/source/includes/ror-compatibility-table-mongoid.rst b/source/includes/ror-compatibility-table-mongoid.rst new file mode 100644 index 00000000..b8bd7ee9 --- /dev/null +++ b/source/includes/ror-compatibility-table-mongoid.rst @@ -0,0 +1,70 @@ +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :class: compatibility-large + :widths: 20 20 60 + + * - Rails Framework + - {+odm+} Support + - Notes + + * - ``ActionCable`` + - ✓ + - There is no MongoDB adapter for ``ActionCable``, but you can + use any existing adapter, such as the `Redis Adapter + <{+active-record-docs+}/action_cable_overview.html#redis-adapter>`__, + alongside {+odm+} models. + + * - ``ActionMailbox`` + - *Unsupported* + - Depends directly on Active Record. + + * - ``ActionMailer`` + - ✓ + - + + * - ``ActionPack`` + - ✓ + - + + * - ``ActionText`` + - *Unsupported* + - Depends directly on Active Record. + + * - ``ActionView`` + - ✓ + - + + * - ``ActiveJob`` + - ✓ + - Serialization of BSON and {+odm+} objects works best if you + explicitly send ``BSON::ObjectId`` values as strings, and + reconstitute them in the job. For example: + + .. code-block:: ruby + + record = Model.find(...) + MyJob.perform_later(record._id.to_s) + + class MyJob < ApplicationJob + def perform(id_as_string) + record = Model.find(id_as_string) + # ... + end + end + + * - ``ActiveModel`` + - ✓ + - The ``Mongoid::Document`` module includes + ``ActiveModel::Model`` and leverages ``ActiveModel::Validations`` + for :ref:`mongoid-modeling-validation`. + + * - ``ActiveStorage`` + - *Unsupported* + - Depends directly on Active Record. + + * - ``ActiveSupport`` + - ✓ + - The ``Mongoid`` module requires ``ActiveSupport``. + ``Mongoid`` uses ``ActiveSupport::TimeWithZone`` for handling + time values. diff --git a/source/includes/ruby-driver-compatibility-table-mongoid.rst b/source/includes/ruby-driver-compatibility-table-mongoid.rst new file mode 100644 index 00000000..8eded198 --- /dev/null +++ b/source/includes/ruby-driver-compatibility-table-mongoid.rst @@ -0,0 +1,14 @@ +.. list-table:: + :header-rows: 1 + :stub-columns: 1 + :class: compatibility-large + + * - {+odm+} Version + - {+language+} Driver 2.18 to 2.21 + - {+language+} Driver 2.10 to 2.17 + - {+language+} Driver 2.7 to 2.9 + + * - 8.0 to 9.0 + - ✓ + - + - diff --git a/source/index.txt b/source/index.txt index 5ec4d9ba..b3cd4534 100644 --- a/source/index.txt +++ b/source/index.txt @@ -12,7 +12,7 @@ MongoDB in Ruby. To work with {+odm+} from the command line using .. toctree:: :titlesonly: - Quick Start - Ruby on Rails + Quick Start - {+ror+} Quick Start - Sinatra Add {+odm+} to an Existing Application Interact with Data @@ -20,7 +20,8 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Configuration /working-with-data API Documentation - /whats-new + What's New + Compatibility Issues & Help /additional-resources /ecosystem diff --git a/source/installation-configuration.txt b/source/installation-configuration.txt deleted file mode 100644 index fd5ea05b..00000000 --- a/source/installation-configuration.txt +++ /dev/null @@ -1,25 +0,0 @@ -.. _installation-configuration: - -**************************** -Installation & Configuration -**************************** - -.. default-domain:: mongodb - -.. toctree:: - :titlesonly: - - installation - reference/compatibility - reference/configuration - reference/rails-integration - -Overview --------- - -Learn how to install and configure Mongoid in the following sections: - -- :ref:`Installation ` -- :ref:`Compatibility ` -- :ref:`Configuration ` -- :ref:`Rails Integration ` \ No newline at end of file diff --git a/source/installation.txt b/source/installation.txt deleted file mode 100644 index bd9e2908..00000000 --- a/source/installation.txt +++ /dev/null @@ -1,66 +0,0 @@ -.. _installation: - -************ -Installation -************ - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Install the Gem -=============== - -Mongoid is bundled as a gem, and is `hosted on Rubygems -`_. -It can be installed manually or with bundler. - -To install the gem manually: - -.. code-block:: sh - - gem install mongoid - -To install the gem with bundler, include the following in your ``Gemfile``: - -.. code-block:: ruby - - gem 'mongoid' - -Using Mongoid with a New Rails Application -========================================== - -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 -================================================ - -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/source/legacy-files/upgrading.txt b/source/legacy-files/upgrading.txt index db27af62..3b5b39bc 100644 --- a/source/legacy-files/upgrading.txt +++ b/source/legacy-files/upgrading.txt @@ -51,7 +51,7 @@ 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. -- *Upgrade Ruby and Rails:* See `"Upgrading Ruby on Rails" `_ +- *Upgrade Ruby and Rails:* See `"Upgrading {+ror+}" `_ for more information diff --git a/source/nesting-levels.txt b/source/nesting-levels.txt deleted file mode 100644 index 2973410d..00000000 --- a/source/nesting-levels.txt +++ /dev/null @@ -1,20 +0,0 @@ -This file is not part of Mongoid documentation proper, it is an internal -reference for the nesting levels that other files should be using. - -Mongoid documentation nesting levels: - -********** -Page Title -********** - -First Level Heading -=================== - -Second Level Heading --------------------- - -Third Level Heading -``````````````````` - -Fourth Level Heading -~~~~~~~~~~~~~~~~~~~~ diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index 659820ca..f2db9fd5 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -1,7 +1,7 @@ .. _mongoid-quick-start-rails: =========================== -Quick Start - Ruby on Rails +Quick Start - {+ror+} =========================== .. facet:: @@ -20,7 +20,7 @@ Quick Start - Ruby on Rails Overview -------- -This guide shows you how to use {+odm+} in a new **Ruby on Rails 8 (Rails)** +This guide shows you how to use {+odm+} in a new **{+ror+} 8 (Rails)** web application, connect to a MongoDB cluster hosted on MongoDB Atlas, and perform read and write operations on the data in your cluster. @@ -38,13 +38,13 @@ To learn how to integrate {+odm+} into an existing application, see the Ruby. By using {+odm+}, you can easily interact with your data and create flexible data models. -Ruby on Rails is a web application framework for +{+ror+} is a web application framework for {+language+}. Rails applications use a model-view-controller (MVC) architecture that allows you to easily control how your data is modeled and displayed. {+odm+} replaces the default Active Record adapter for data modeling in Rails. -To learn more about Ruby on Rails, see the `Getting Started +To learn more about {+ror+}, see the `Getting Started with Rails <{+active-record-docs+}/getting_started.html>`__ guide in the Rails documentation. diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index cca1e8af..cf7ae849 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -14,7 +14,7 @@ Download and Install Prerequisites ------------- -To create the Quick Start application by using Ruby on Rails 8, you need the +To create the Quick Start application by using {+ror+} 8, you need the following software installed in your development environment: - `{+language+}. `__ diff --git a/source/quick-start-rails/next-steps.txt b/source/quick-start-rails/next-steps.txt index 9e7bcde4..1869ddf1 100644 --- a/source/quick-start-rails/next-steps.txt +++ b/source/quick-start-rails/next-steps.txt @@ -11,8 +11,7 @@ Next Steps .. meta:: :keywords: learn more -Congratulations on completing the Quick Start tutorial for Ruby on Rails -7! +Congratulations on completing the Quick Start tutorial for {+ror+} 8! After you complete these steps, you have a Rails web application that uses {+odm+} to connect to your MongoDB deployment, run a query on diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index db7a785d..a5fb7bc7 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -50,7 +50,7 @@ that connects to a MongoDB deployment. .. tip:: Other Framework Tutorials - If you prefer to use Ruby on Rails 8 as your web framework, see the + If you prefer to use {+ror+} 8 as your web framework, see the :ref:`mongoid-quick-start-rails` guide. .. TODO .. tip:: diff --git a/source/reference/compatibility.txt b/source/reference/compatibility.txt deleted file mode 100644 index 7dd2ae19..00000000 --- a/source/reference/compatibility.txt +++ /dev/null @@ -1,507 +0,0 @@ -.. _mongoid-compatibility: - -============= -Compatibility -============= - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Ruby MongoDB Driver Compatibility ---------------------------------- - -The following compatibility table specifies the versions of `Ruby driver for -MongoDB `_ -(the ``mongo`` gem) supported by the most recent patch releases of the -specified Mongoid versions. - -.. note:: - - Older versions of Mongoid within the same minor release may support older - driver versions. For example, Mongoid 7.0.5 supports driver versions 2.5 and - newer, whereas Mongoid 7.0.6 requires driver version 2.7 or newer. - -.. list-table:: - :header-rows: 1 - :stub-columns: 1 - :class: compatibility-large no-padding - - * - Mongoid - - Driver 2.18+ - - Driver 2.17-2.10 - - Driver 2.9-2.7 - - * - 8.0 through 9.0 - - |checkmark| - - - - - - * - 7.2 through 7.5 - - |checkmark| - - |checkmark| - - - - * - 7.0 through 7.1 - - |checkmark| - - |checkmark| - - |checkmark| - - -Ruby Compatibility ------------------- - -The following compatibility table specifies the versions of Ruby interpreters -supported by Mongoid. "D" in a column means support for that Ruby version -is deprecated. - -.. list-table:: - :header-rows: 1 - :stub-columns: 1 - :class: compatibility-large no-padding - - * - Mongoid - - Ruby 3.2 - - Ruby 3.1 - - Ruby 3.0 - - Ruby 2.7 - - Ruby 2.6 - - Ruby 2.5 - - Ruby 2.4 - - Ruby 2.3 - - Ruby 2.2 - - JRuby 9.4 - - JRuby 9.3 - - JRuby 9.2 - - * - 9.0 - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - - - |checkmark| - - - - - - * - 8.1 - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - - - |checkmark| - - - - * - 8.0 - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - - - |checkmark| - - - - * - 7.5 - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - D - - - - - - - - - - |checkmark| - - D - - * - 7.4 - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - - - |checkmark| - - * - 7.3 - - - - |checkmark| [#mongoid-7.3-ruby-3.0]_ - - |checkmark| [#mongoid-7.3-ruby-3.0]_ - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - - - - - - - |checkmark| - - * - 7.2 - - - - |checkmark| [#mongoid-7.2-ruby-3.0]_ - - |checkmark| [#mongoid-7.2-ruby-3.0]_ - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - - - - - - - |checkmark| - - * - 7.1 - - - - |checkmark| [#mongoid-7.1-ruby-3.0]_ - - |checkmark| [#mongoid-7.1-ruby-3.0]_ - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| [#ruby-2.4]_ - - |checkmark| - - - - - - - - |checkmark| - - * - 7.0 - - - - - - - - |checkmark| - - |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. - -.. [#mongoid-7.2-ruby-3.0] Mongoid version 7.2.5 or higher is required. - -.. [#mongoid-7.1-ruby-3.0] Mongoid version 7.1.10 or higher is required. - -.. [#ruby-2.4] Ruby version 2.4.1 or higher is required. - -.. [#ruby-2.2] Ruby version 2.2.2 or higher is required. - - -MongoDB Server Compatibility ----------------------------- - -The following compatibility table specifies the recommended -version(s) of Mongoid for use with a specific version of MongoDB server. - -Note that in order to use features of a particular MongoDB server version, -both the driver and Mongoid must support that server version. -Please refer to `the driver compatibility page -`_ -for driver compatibility matrices. - -"D" in a column means support for that MongoDB server version is deprecated -and will be removed in a next version. - -.. list-table:: - :header-rows: 1 - :stub-columns: 1 - :class: compatibility-large no-padding - - * - Mongoid - - MongoDB 8.0 - - MongoDB 7.0 - - MongoDB 6.0 - - MongoDB 5.0 - - MongoDB 4.4 - - MongoDB 4.2 - - MongoDB 4.0 - - MongoDB 3.6 - - MongoDB 3.4 - - MongoDB 3.2 - - MongoDB 3.0 - - MongoDB 2.6 - - * - 9.0+ - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - * - 8.0 through 9.0 - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - - - - - * - 7.4 through 7.5 - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - D - - D - - * - 7.0 through 7.3 - - - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - D - - D - - D - - D - - -.. _rails-compatibility: - -Rails Compatibility -------------------- - -The following compatibility table specifies which versions of Ruby on Rails -are supported by Mongoid. - -.. list-table:: - :header-rows: 1 - :stub-columns: 1 - :class: compatibility-large no-padding - - * - Mongoid - - Rails 7.2 - - Rails 7.1 - - Rails 7.0 - - Rails 6.1 - - Rails 6.0 - - Rails 5.2 - - Rails 5.1 - - * - 9.0 - - |checkmark| [#rails-7.2]_ - - |checkmark| [#rails-7.1]_ - - |checkmark| - - |checkmark| - - |checkmark| - - - - - - * - 8.1 - - |checkmark| [#rails-7.2]_ - - |checkmark| [#rails-7.1]_ - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| [#rails-5-ruby-3.0]_ - - - - * - 8.0 - - - - |checkmark| [#rails-7.1]_ - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| [#rails-5-ruby-3.0]_ - - - - * - 7.5 - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| [#rails-5-ruby-3.0]_ - - D - - * - 7.4 - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| [#rails-5-ruby-3.0]_ - - |checkmark| [#rails-5-ruby-3.0]_ - - * - 7.3 - - - - - - |checkmark| [#rails-7-Mongoid-7.3]_ - - |checkmark| - - |checkmark| - - |checkmark| [#rails-5-ruby-3.0]_ - - |checkmark| [#rails-5-ruby-3.0]_ - - * - 7.2 - - - - - - - - |checkmark| [#rails-6.1]_ - - |checkmark| - - |checkmark| [#rails-5-ruby-3.0]_ - - |checkmark| [#rails-5-ruby-3.0]_ - - * - 7.1 - - - - - - - - |checkmark| [#rails-6.1]_ - - |checkmark| - - |checkmark| - - |checkmark| - - * - 7.0 - - - - - - - - |checkmark| [#rails-6.1]_ - - |checkmark| [#rails-6]_ - - |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. - -.. [#rails-6.1] Rails 6.1 requires Mongoid 7.0.12, 7.1.7 or 7.2.1 in the - respective 7.0, 7.1 and 7.2 stable branches. - -.. [#rails-7-Mongoid-7.3] Rails 7.x requires Mongoid 7.3.4 or later. - -.. [#rails-7.1] Rails 7.1 requires Mongoid 8.0.7 or 8.1.3 in the respective - 8.0 and 8.1 stable branches. - -.. [#rails-7.2] Rails 7.2 requires Mongoid 8.1.6 and 9.0.2 in the respective - 8.1 and 9.0 stable branches. - -.. include:: /includes/unicode-checkmark.rst -.. include:: /includes/unicode-ballot-x.rst - -Rails Frameworks Support -~~~~~~~~~~~~~~~~~~~~~~~~ - -Ruby on Rails is comprised of a number of frameworks, which Mongoid attempts to -provide compatibility with wherever possible. - -Though Mongoid attempts to offer API compatibility with `Active Record `_, -libraries that depend directly on Active Record may not work as expected when -Mongoid is used as a drop-in replacement. - -.. note:: - - Mongoid can be used alongside Active Record within the same application without issue. - -.. list-table:: - :header-rows: 1 - :stub-columns: 1 - :class: compatibility-large no-padding - - * - Rails Framework - - Supported? - - * - ``ActionCable`` - - |checkmark| [#rails-actioncable-dependency]_ - - * - ``ActionMailbox`` - - |x| [#rails-activerecord-dependency]_ - - * - ``ActionMailer`` - - |checkmark| - - * - ``ActionPack`` - - |checkmark| - - * - ``ActionText`` - - |x| [#rails-activerecord-dependency]_ - - * - ``ActionView`` - - |checkmark| - - * - ``ActiveJob`` - - |checkmark| [#rails-activejob-dependency]_ - - * - ``ActiveModel`` - - |checkmark| [#rails-activemodel-dependency]_ - - * - ``ActiveStorage`` - - |x| [#rails-activerecord-dependency]_ - - * - ``ActiveSupport`` - - |checkmark| [#rails-activesupport-dependency]_ - -.. [#rails-actioncable-dependency] There is currently no MongoDB adapter for - ``ActionCable``, however any existing adapter (such as `Redis `_) - can be used successfully in conjunction with Mongoid models - -.. [#rails-activerecord-dependency] Depends directly on ActiveRecord - -.. [#rails-activemodel-dependency] ``Mongoid::Document`` includes ``ActiveModel::Model`` - and leverages ``ActiveModel::Validations`` for validations - -.. [#rails-activesupport-dependency] ``Mongoid`` requires ``ActiveSupport`` and - uses it extensively, including ``ActiveSupport::TimeWithZone`` for time handling. - - - -.. [#rails-activejob-dependency] Serialization of BSON & Mongoid objects works best - if you explicitly send ``BSON::ObjectId``'s as strings, and reconstitute them in the job: - - .. code-block:: ruby - - record = Model.find(...) - MyJob.perform_later(record._id.to_s) - - class MyJob < ApplicationJob - def perform(id_as_string) - record = Model.find(id_as_string) - # ... - end - end diff --git a/source/reference/configuration.txt b/source/reference/configuration.txt index 2d82faf2..c948673d 100644 --- a/source/reference/configuration.txt +++ b/source/reference/configuration.txt @@ -36,7 +36,7 @@ Most applications will use a single client named ``default``. Generating Default Configuration ================================ -If you are using Ruby on Rails, you can have Mongoid generate a default +If you are using {+ror+}, you can have Mongoid generate a default configuration file for you by running the following command: .. code-block:: bash @@ -51,14 +51,14 @@ initializer may also be used to set configuration options. Note, though, that settings in ``mongoid.yml`` always take precedence over settings in the initializer. -If you are not using Ruby on Rails, you can copy the minimal configuration +If you are not using {+ror+}, you can copy the minimal configuration given above and save it as ``config/mongoid.yml``. Loading Mongoid Configuration ============================= -If you are using Ruby on Rails, Mongoid configuration is automatically loaded +If you are using {+ror+}, Mongoid configuration is automatically loaded for the current environment as stored in ``Rails.env`` when the application loads. @@ -71,7 +71,7 @@ adding the following to ``application.rb``: g.orm :mongoid end -If you are not using Ruby on Rails, Mongoid configuration must be loaded +If you are not using {+ror+}, Mongoid configuration must be loaded manually. This can be done via the ``Mongoid.load!`` method, which takes the configuration file path as its argument, as follows: @@ -509,14 +509,14 @@ logger. In other words: # Ruby driver logger, not Mongoid logger Mongoid.client(:default).logger -Depending on whether Mongoid is used in a Ruby on Rails application, and how +Depending on whether Mongoid is used in a {+ror+} application, and how both Mongoid and Ruby driver are configured, they may use the same logger instance or different instances, potentially with different configurations. -In Ruby on Rails Application +In {+ror+} Application ---------------------------- -When used in a Ruby on Rails application, Mongoid by default inherits +When used in a {+ror+} application, Mongoid by default inherits the logger and the log level from Rails, and sets the driver's logger to the same logger instance: @@ -588,7 +588,7 @@ use an initializer as follows: Standalone ---------- -When not loaded in a Ruby on Rails application, Mongoid respects the +When not loaded in a {+ror+} application, Mongoid respects the ``log_level`` top level configuration option. It can be given in the configuration file as follows: @@ -963,7 +963,7 @@ Enabling Query Cache for Rack Web Requests The MongoDB Ruby Driver provides a Rack middleware which enables the :ref:`Query Cache ` for the duration of each web request. Below is an example of -how to enable the Query Cache Middleware in a Ruby on Rails application: +how to enable the Query Cache Middleware in a {+ror+} application: .. code-block:: ruby diff --git a/source/reference/rails-integration.txt b/source/reference/rails-integration.txt index 0a43d96d..15ea483a 100644 --- a/source/reference/rails-integration.txt +++ b/source/reference/rails-integration.txt @@ -12,7 +12,7 @@ Rails Integration :depth: 2 :class: singlecol -Mongoid seamlessly integrates into Ruby on Rails applications. +Mongoid seamlessly integrates into {+ror+} applications. This page describes features that are automatically enabled in the context of a Rails application and Rails-related functionality which can be manually enabled. diff --git a/source/release-notes.txt b/source/release-notes.txt deleted file mode 100644 index 27127103..00000000 --- a/source/release-notes.txt +++ /dev/null @@ -1,31 +0,0 @@ -.. _release-notes: - -************* -Release Notes -************* - -.. default-domain:: mongodb - -.. toctree:: - :titlesonly: - - release-notes/upgrading - release-notes/mongoid-9.0 - release-notes/mongoid-8.1 - release-notes/mongoid-8.0 - release-notes/mongoid-7.5 - release-notes/mongoid-7.4 - release-notes/mongoid-7.3 - -Overview --------- - -See the following sections to learn more about upgrading Mongoid: - -- :ref:`Upgrading Mongoid ` -- :ref:`Mongoid 9.0 ` -- :ref:`Mongoid 8.1 ` -- :ref:`Mongoid 8.0 ` -- :ref:`Mongoid 7.5 ` -- :ref:`Mongoid 7.4 ` -- :ref:`Mongoid 7.3 ` \ No newline at end of file diff --git a/source/whats-new.txt b/source/whats-new.txt index e6b0bbd7..e6e52baa 100644 --- a/source/whats-new.txt +++ b/source/whats-new.txt @@ -33,7 +33,7 @@ for creating, updating, managing, and maintaining Rails applications, is generally available. ``railsmdb`` makes it easier to work with MongoDB from the command line -through common generators that {+language+} on Rails developers are already +through common generators that {+ror+} developers are already familiar with. For example, you can use ``railsmdb`` to generate stubs for new {+odm+} models: diff --git a/source/working-with-data.txt b/source/working-with-data.txt deleted file mode 100644 index 96ab61dc..00000000 --- a/source/working-with-data.txt +++ /dev/null @@ -1,37 +0,0 @@ -.. _working-with-data: - -***************** -Working With Data -***************** - -.. default-domain:: mongodb - -.. toctree:: - :titlesonly: - - reference/crud - reference/queries - reference/text-search - /aggregation - reference/map-reduce - reference/persistence-configuration - reference/nested-attributes - reference/callbacks - reference/sessions - reference/transactions - -Overview --------- - -See the following sections to learn more about working with data in Mongoid: - -- :ref:`CRUD Operations ` -- :ref:`Queries ` -- :ref:`Text Search ` -- :ref:`mongoid-aggregation` -- :ref:`Map/Reduce ` -- :ref:`Persistence Configuration ` -- :ref:`Nested Attributes ` -- :ref:`Callbacks ` -- :ref:`Sessions ` -- :ref:`Transactions ` \ No newline at end of file From 3c4d189db9c91ce58010083be60e6eaeb7aa64e5 Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:18:00 -0800 Subject: [PATCH 105/113] DOCSP-45366 Encryption (#89) --- snooty.toml | 1 + source/includes/security/encryption.rb | 102 +++++ source/index.txt | 3 +- source/security.txt | 21 ++ source/security/encryption.txt | 333 ++++++++++++++++ source/tutorials.txt | 4 +- source/tutorials/automatic-encryption.txt | 439 ---------------------- 7 files changed, 460 insertions(+), 443 deletions(-) create mode 100644 source/includes/security/encryption.rb create mode 100644 source/security.txt create mode 100644 source/security/encryption.txt delete mode 100644 source/tutorials/automatic-encryption.txt diff --git a/snooty.toml b/snooty.toml index 6d6f5a8f..02e25f34 100644 --- a/snooty.toml +++ b/snooty.toml @@ -34,3 +34,4 @@ server-manual = "Server manual" api = "https://www.mongodb.com/docs/mongoid/current/api" ruby-api = "https://www.mongodb.com/docs/ruby-driver/current/api" active-record-docs = "https://guides.rubyonrails.org" +shared-library = "Automatic Encryption Shared Library" diff --git a/source/includes/security/encryption.rb b/source/includes/security/encryption.rb new file mode 100644 index 00000000..e2ee5555 --- /dev/null +++ b/source/includes/security/encryption.rb @@ -0,0 +1,102 @@ +# start-encryption-schema +class Patient + include Mongoid::Document + include Mongoid::Timestamps + + encrypt_with key_id: '' + + # This field is not encrypted + field :category, type: String + + # This field is encrypted by using AEAD_AES_256_CBC_HMAC_SHA_512-Random + # algorithm + field :passport_id, type: String, encrypt: { + deterministic: false + } + + # This field is encrypted by using AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic + # algorithm + field :blood_type, type: String, encrypt: { + deterministic: true + } + + # This field is encrypted by using AEAD_AES_256_CBC_HMAC_SHA_512-Random + # algorithm and a different data key + field :ssn, type: Integer, encrypt: { + deterministic: false, key_id: '') + +# Create the encryption object +encryption = Mongo::ClientEncryption.new( + key_vault_client, + key_vault_namespace: 'encryption.__keyVault', + kms_providers: { + aws: { + "accessKeyId": "", + "secretAccessKey": "" + } + } +) + +encryption.rewrap_many_data_key( + {}, # Empty filter to rewrap all keys + { + provider: 'aws', + master_key: { + region: 'us-east-2', + key: 'arn:aws:kms:us-east-2:...' + } + } +) +# end-rewrap-keys + +# start-in-place + +# Print all documents in the collection. The first document is unencrypted, and +# the second is encrypted. +Patient.all.to_a +# => +# [#, +# #] + +# Querying for documents with a CSFLE-enabled client returns only the encrypted document +Patient.where(blood_type: 'AB+').to_a +# => [#] +# end-in-place \ No newline at end of file diff --git a/source/index.txt b/source/index.txt index b3cd4534..ae3e2931 100644 --- a/source/index.txt +++ b/source/index.txt @@ -18,10 +18,11 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Interact with Data Model Your Data Configuration + Secure Your Data /working-with-data API Documentation What's New Compatibility Issues & Help /additional-resources - /ecosystem + /ecosystem \ No newline at end of file diff --git a/source/security.txt b/source/security.txt new file mode 100644 index 00000000..3b8db0b6 --- /dev/null +++ b/source/security.txt @@ -0,0 +1,21 @@ +.. _mongoid-security: + +================ +Secure Your Data +================ + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, security + +.. toctree:: + :caption: Secure Your Data + + In-Use Encryption + +In this section, you can learn how to secure your data when using {+odm+}. + +- :ref:`Client-Side Field Level Encryption ` Learn how to encrypt your data with {+odm+}. \ No newline at end of file diff --git a/source/security/encryption.txt b/source/security/encryption.txt new file mode 100644 index 00000000..904affc0 --- /dev/null +++ b/source/security/encryption.txt @@ -0,0 +1,333 @@ +.. _automatic-encryption: +.. _mongoid-encryption: + +================================== +Client-Side Field Level Encryption +================================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, security, encrypt data, csfle, code example + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to encrypt your data by using **client-side +field level encryption** (CSFLE). CSFLE allows you to encrypt data in your +application before sending it over the network to MongoDB. This means that no +MongoDB product has access to your data in unencrypted form. + +You can set up CSFLE by using one of the following mechanisms: + +- Automatic encryption: Allows you to perform encrypted read and write + operations without specifying how to encrypt fields +- Explicit encryption: Allows you to perform encrypted read and write operations + with specified encryption logic throughout your application. + +This guide describes how to set up CSFLE with automatic encryption. To learn more +about using explicit encryption, see the :ruby:`Explicit Encryption +` guide +in the {+ruby-driver+} documentation. + +Install Dependencies +-------------------- + +To use CSFLE with {+odm+}, you must first install the following dependencies: + +- ``libmongocrypt`` +- {+shared-library+} if you are using {+ruby-driver+} v2.19 or later. Or + ``mongocryptd`` if you are using {+ruby-driver+} v2.18 or earlier. +- ``ffi`` + +The following sections provide details on how to install these +dependencies. + +libmongocrypt +~~~~~~~~~~~~~ + +You can install ``libmongocrypt`` by adding the :github:`libmongocrypt-helper gem +` to your ``Gemfile`` +or by downloading the library manually. + +To install ``libmongocrypt`` by adding the gem file, navigate to the folder in +which your application is located and run the following command in your shell: + +.. code-block:: bash + + gem install libmongocrypt-helper --pre + +.. note:: + + Because the version number of ``libmongocrypt-helper`` might contain letters, + which indicates a pre-release version in {+language+}, the ``--pre`` flag is + required. + +To learn how to download and install the library manually, see the +:ruby:`libmongocrypt installation guide +` +in the {+ruby-driver+} documentation. + +Shared Library (Driver v2.19 or later) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following steps detail how to install the {+shared-library+}: + +1. In a browser, navigate to the :website:`MongoDB Download Center + `. +#. Select the latest current version in the ``Version`` dropdown, denoted by the + ``(current)`` tag. +#. Select your platform in the ``Platform`` dropdown. +#. Select ``crypt_shared`` in the ``Package`` dropdown. +#. Click the ``Download`` button to download the shared library. + +After you download the file, extract the contents and save the +file in a location that your application can access. Then, configure your +``mongoid.yml`` file in your application to point to the library, as shown in +the following example: + +.. code-block:: ruby + :emphasize-lines: 5-7 + + development: + clients: + default: + options: + auto_encryption_options: + extra_options: + crypt_shared_lib_path: '' + +mongocryptd (Driver v2.18 or earlier) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are using {+ruby-driver+} version 2.18 or earlier, you must use ``mongocryptd`` +instead of the {+shared-library+}. ``mongocryptd`` comes +pre-packaged with enterprise builds of MongoDB Server. For instructions on how to +install and configure ``mongocryptd``, see the :manual:`Install mongocryptd guide +` in the MongoDB +{+server-manual+}. + +ffi +~~~ + +{+odm+} uses the `ffi gem `__ to call functions from +``libmongocrypt``. Add the gem to your ``Gemfile`` by running the following +command in your shell: + +.. code-block:: bash + + gem 'ffi' + +Create a Customer Master Key +---------------------------- + +To encrypt and decrypt data, you must first create a Customer Master Key (CMK). +A CMK is a key that you use to encrypt your :ref:`Data Encryption Key +` (DEK). Without access to a CMK, your client application cannot decrypt +DEKs associated with your encrypted data. + +You can create a locally-stored key to use as a local CMK for testing purposes +by running the following {+language+} code: + +.. code-block:: ruby + + require 'securerandom' + + SecureRandom.hex(48) + +.. warning:: + + Using a local CMK in a production environment is insecure. For production + environments, use a remote key management service to create and + store your CMK. + + To learn more about key management service providers, see the :manual:`KMS Providers + guide ` in the MongoDB + {+server-manual+}. + +Configure your Client +--------------------- + +You must configure your MongoDB client to implement CSFLE. To configure a +client for CSFLE, add the following code to your ``mongoid.yml`` file: + +.. code-block:: bash + + development: + clients: + default: + uri: "" + options: + auto_encryption_options: # This key enables automatic encryption + key_vault_namespace: 'encryption.__keyVault' # Database and collection in which to store data keys + kms_providers: # Tells the driver where to obtain master keys + local: # Specifies that the key is local + key: "" + extra_options: + crypt_shared_lib_path: '' # Only required for Ruby versions 2.19 or later + +.. note:: + + Ensure that you replace the placeholders surrounded by brackets (``<>``) in the preceding code + example. + +.. _mongoid-dek: + +Create a Data Encryption Key +---------------------------- + +A Data Encryption Key (DEK) is a key that you use to encrypt the fields in your +documents. MongoDB stores DEKs, encrypted with your CMK, in the Key Vault +collection as BSON documents. MongoDB can never decrypt the DEKs, as key +management is client-side and customer controlled. + +To create a DEK in {+odm+}, you can use the +``db:mongoid:encryption:create_data_key`` rake task, as shown in the following +example: + +.. code-block:: bash + + rake db:mongoid:encryption:create_data_key + +You can create multiple DEKs by repeating the preceding command for the number +of keys you want to generate. + +You can also provide an alternate name for your DEK. This allows you to reference +the DEK by name when configuring encryption for your fields and +to dynamically assign a DEK to a field at runtime. + +The following example creates an alternate name when generating a new DEK: + +.. code-block:: bash + + rake db:mongoid:encryption:create_data_key -- --key-alt-name= + +Configure Encryption Schema +--------------------------- + +You can specify which fields to encrypt by adding the ``encrypt`` option to the +field definition in your models and specifying the ``deterministic`` and +``key_id`` options, as shown in the following example: + +.. literalinclude:: /includes/security/encryption.rb + :language: ruby + :start-after: start-encryption-schema + :end-before: end-encryption-schema + +.. note:: + + If you are developing a Rails application, we recommend setting the + ``preload_models`` option to ``true`` in your ``mongoid.yml`` file. This + ensures that {+odm+} loads all models and configures the encryption schema + before any data is read or written. + +Limitations +----------- + +The following limitations apply when using CSFLE with {+odm+}: + +- {+odm+} does not support encryption of ``embeds_many`` associations. +- If you use the ``:key_name_field`` option, you must encrypt the field by using + a non-deterministic algorithm. To encrypt your field deterministically, you must + specify the ``:key_id`` option instead. +- The limitations listed on the :manual:`CSFLE Limitations + ` page in the MongoDB {+server-manual+} + also apply to {+odm+}. + +Working with Data +----------------- + +Usually, automatic CSLFE works transparently in your application. After +your application is configured for CSFLE, you can create documents as usual and {+odm+} +automatically encrypts and decrypts them according to your configuration. + +The following example creates a new ``Patient`` document in an application +configured for CSFLE. It then uses a client called ``unencrypted_client`` that +is connected to the database but not configured for CSFLE to read the document. + +.. io-code-block:: + + .. input:: /includes/security/encryption.rb + :language: ruby + :start-after: start-query-encrypted + :end-before: end-query-encrypted + + .. output:: + + {"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4292'), + "category"=>"ER", + "passport_id"=>, + "blood_type"=>, + "ssn"=>, + "insurance"=>{"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4293'), + "insurer"=>"TK", "policy_number"=>}, "policy_number_key"=>"my_data_key"} + +In the preceding example, the ``unencrypted_client`` client is unable to read +the encrypted fields. However, if you query the document with a client that *is* +configured for CSFLE, {+odm+} automatically decrypts the fields. + +Rotate Encryption Keys +---------------------- + +You can rotate your encryption keys by using the ``rewrap_many_data_key`` {+ruby-driver+} +method. This method automatically decrypts multiple data encryption keys +and re-encrypts them using a specified CMK. It then updates the rotated keys in +the key vault collection. + +The ``rewrap_many_data_key`` method takes the following parameters: + +- Filter, used to specify which fields to rotate. If no data key matches the + given filter, no keys will be rotated. Omit the filter to rotate all keys in + your key vault collection. +- Object that represents a new CMK with which to re-encrypt the DEKs. Omit + this object to rotate the data keys by using their current CMKs. + +The following example rotates encryption keys by using the AWS KMS: + +.. literalinclude:: /includes/security/encryption.rb + :language: ruby + :start-after: start-rewrap-keys + :end-before: end-rewrap-keys + +Add Automatic Encryption to Existing Project +-------------------------------------------- + +Automatic CSFLE with {+odm+} supports encryption in place. You can enable +encryption on an existing database and still read unencrypted data. However, +once you enable encryption, all new data is encrypted, and any query operation +uses only the encrypted documents. This means that queries might not return all documents +if some were saved before enabling encryption. + +The following example queries a collection that has one encrypted document and +one unencrypted document: + +.. literalinclude:: /includes/security/encryption.rb + :language: ruby + :start-after: start-in-place + :end-before: end-in-place + +You can encrypt existing data in a collection by reading and then writing back +all data with a CSFLE-enabled client. When doing so, ensure that all existing +data is the expected type and that empty values are not set as ``nil``. + +Additional Information +---------------------- + +To learn more about CSFLE, see the :manual:`Client-Side Field Level Encryption +` guide in the MongoDB {+server-manual+}. + +To learn more about using CSFLE with the {+ruby-driver+}, see the +:ruby:`Client-Side Encryption +` guide in the +{+ruby-driver+} documentation. + +.. TODO: Add link to mongoid configuration page for encryption settings when available. \ No newline at end of file diff --git a/source/tutorials.txt b/source/tutorials.txt index 8b30c6e0..2a564253 100644 --- a/source/tutorials.txt +++ b/source/tutorials.txt @@ -8,12 +8,10 @@ Tutorials :titlesonly: tutorials/common-errors - tutorials/automatic-encryption Overview -------- See the following sections to learn more about working with Mongoid: -- :ref:`Common Errors ` -- :ref:`Automatic Client-Side Field Level Encryption ` \ No newline at end of file +- :ref:`Common Errors ` \ No newline at end of file diff --git a/source/tutorials/automatic-encryption.txt b/source/tutorials/automatic-encryption.txt deleted file mode 100644 index 62e68c27..00000000 --- a/source/tutorials/automatic-encryption.txt +++ /dev/null @@ -1,439 +0,0 @@ -.. _automatic-encryption: - -******************************************** -Automatic Client-Side Field Level Encryption -******************************************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -Since version 4.2 MongoDB supports `Client-Side Field Level Encryption -(CSFLE) `_. This is a feature -that enables you to encrypt data in your application before you send it over the -network to MongoDB. With CSFLE enabled, no MongoDB product has access to your -data in an unencrypted form. - -You can set up CSFLE using the following mechanisms: - -* `Automatic Encryption `_: - Enables you to perform encrypted read and write operations without you having - to write code to specify how to encrypt fields. -* `Explicit Encryption `_: - Enables you to perform encrypted read and write operations through your - MongoDB driver's encryption library. You must specify the logic for encryption - with this library throughout your application. - -Starting with version 9.0, Mongoid supports CSFLE's Automatic Encryption -feature. This tutorial walks you through the process of setting up and using -CSFLE in Mongoid. - -.. note:: - This tutorial does not cover all CSLFE features. - You can find more information about MongoDB CSFLE in - `the server documentation. `_ - -.. note:: - If you want to use explicit FLE, please follow `the Ruby driver documentation. - `_ - - -Installation -============ - -You can find the detailed description of how to install the necessary -dependencies in `the driver documentation. `_ - -Note the version of the Ruby driver being used in your application and select -the appropriate steps below. - -Install ``libmongocrypt`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -This can be done one of two ways. - -* Add the `libmongocrypt-helper gem `_ to - your ``Gemfile`` or -* Download the ``libmongocrypt`` `release archive `_, - extract the version that matches your operating system, and set the - ``LIBMONGOCRYPT_PATH`` environment variable accordingly. - -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 on the -:manual:`Automatic Encryption Shared Library for Queryable Encryption -` -page in the Server manual. - -The steps required are as follows: - -1. Navigate to the `MongoDB Download Center `_ -2. From the Version dropdown, select ``x.y.z (current)`` (the latest current version). -3. In the Platform dropdown, select your platform. -4. In the Package dropdown, select ``crypt_shared``. -5. Click Download. - -Once extracted, ensure the full path to the library is configured within your -``mongoid.yml`` as follows: - -.. code-block:: yaml - - development: - clients: - default: - options: - auto_encryption_options: - extra_options: - crypt_shared_lib_path: '/path/to/mongo_crypt_v1.so' - -Install the ``mongocryptd`` (Ruby driver 2.18 or older) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you are using an older version of the Ruby driver ``mongocryptd`` will -need to be installed manually. ``mongocryptd`` comes pre-packaged with -enterprise builds of the MongoDB server (versions 4.2 and newer). -For installation instructions, see the `MongoDB manual `_. - -Add ``ffi`` to your Gemfile -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The MongoDB Ruby driver uses the `ffi gem `_ to call -functions from ``libmongocrypt``. As this gem is not a dependency of -the driver, it will need to be manually added to your ``Gemfile``: - -.. code-block:: ruby - - gem 'ffi' - -Create a Customer Master Key -============================ - -A Customer Master Key (CMK) is used to encrypt Data Encryption Keys. -The easiest way is to use a locally stored 96-bytes key. You can generate such -a key using the following Ruby code: - -.. code-block:: ruby - - require 'securerandom' - - SecureRandom.hex(48) # => "f54ab...." - -Later in this tutorial we assume that the Customer Master Key is -available from the ``CUSTOMER_MASTER_KEY`` environment variable. - -.. warning:: - - Using a local master key is insecure. It is recommended that you use a remote - Key Management Service to create and store your master key. To do so, follow - steps of the `"Set up a Remote Master Key" `_ - in the MongoDB Client-Side Encryption documentation. - - For more information about creating a master key, see the - `Create a Customer Master Key `_ - section of the MongoDB manual. - -Configure Clients -================= - -Automatic CSFLE requires some additional configuration for the MongoDB client. -Assuming that your application has just one ``default`` client, you need to -add the following to your ``mongoid.yml``: - -.. code-block:: yaml - - development: - clients: - default: - uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority - options: - auto_encryption_options: # This key enables automatic encryption - key_vault_namespace: 'encryption.__keyVault' # Database and collection to store data keys - kms_providers: # Tells the driver where to obtain master keys - local: # We use the local key in this tutorial - key: "<%= ENV['CUSTOMER_MASTER_KEY'] %>" # Key that we generated earlier - extra_options: - crypt_shared_lib_path: '/path/to/mongo_crypt_v1.so' # Only if you use the library - - -Create a Data Encryption Key -============================ - -A Data Encryption Key (DEK) is the key you use to encrypt the fields in your -MongoDB documents. You store your Data Encryption Key in your Key Vault -collection encrypted with your CMK. - -To create a DEK in Mongoid you can use the -``db:mongoid:encryption:create_data_key`` ``Rake`` task: - -.. code-block:: sh - - % rake db:mongoid:encryption:create_data_key - Created data key with id: 'KImyywsTQWi1+cFYIHdtlA==' for kms provider: 'local' in key vault: 'encryption.__keyVault'. - -You can create multiple DEKs, if necessary. - -.. code-block:: sh - - % rake db:mongoid:encryption:create_data_key - Created data key with id: 'Vxr5m+5cQISjDOruzZgE0w==' for kms provider: 'local' in key vault: 'encryption.__keyVault'. - -You can also provide an alternate name for the DEK. This allows you to reference -the DEK by name when configuring encryption for your fields. It also allows you -to dynamically assign a DEK to a field at runtime. - -.. code-block:: sh - - % rake db:mongoid:encryption:create_data_key -- --key-alt-name=my_data_key - Created data key with id: 'yjF8hKmKQsqGeFGXlB9Sow==' with key alt name: 'my_data_key' for kms provider: 'local' in key vault: 'encryption.__keyVault'. - - -Configure Encryption Schema -=========================== - -Now we can tell Mongoid what should be encrypted: - -.. code-block:: ruby - - class Patient - include Mongoid::Document - include Mongoid::Timestamps - - # Tells Mongoid what DEK should be used to encrypt fields of the document - # and its embedded documents. - encrypt_with key_id: 'KImyywsTQWi1+cFYIHdtlA==' - - # This field is not encrypted. - field :category, type: String - - # This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random - # algorithm. - field :passport_id, type: String, encrypt: { - deterministic: false - } - # This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic - # algorithm. - field :blood_type, type: String, encrypt: { - deterministic: true - } - # This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random - # algorithm and using a different data key. - field :ssn, type: Integer, encrypt: { - deterministic: false, key_id: 'Vxr5m+5cQISjDOruzZgE0w==' - } - - embeds_one :insurance - end - - class Insurance - include Mongoid::Document - include Mongoid::Timestamps - - field :insurer, type: String - - # This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random - # algorithm using the key which alternate name is stored in the - # policy_number_key field. - field :policy_number, type: Integer, encrypt: { - deterministic: false, - key_name_field: :policy_number_key - } - - embedded_in :patient - end - -.. note:: - If you are developing a Rails application, it is recommended to set - ``preload_models`` to ``true`` in ``mongoid.yml``. This will ensure that - Mongoid loads all models before the application starts, and the encryption - schema is configured before any data is read or written. - -Known Limitations -~~~~~~~~~~~~~~~~~ - -* 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 - specify ``:key_id`` option instead. - -Working with Data -================= - -Automatic CSFLE usage is transparent in many situations. - -.. note:: - In code examples below we assume that there is a variable ``unencrypted_client`` - that is a ``MongoClient`` connected to the same database but without encryption. - We use this client to demonstrate what is actually persisted in the database. - -Documents can be created as usual, fields will be encrypted and decrypted -according to the configuration: - -.. code-block:: ruby - - Patient.create!( - category: 'ER', - passport_id: '123456', - blood_type: 'AB+', - ssn: 98765, - insurance: Insurance.new(insurer: 'TK', policy_number: 123456, policy_number_key: 'my_data_key') - ) - - # Fields are encrypted in the database - unencrypted_client['patients'].find.first - # => - # {"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4292'), - # "category"=>"ER", - # "passport_id"=>, - # "blood_type"=>, - # "ssn"=>, - # "insurance"=>{"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4293'), "insurer"=>"TK", "policy_number"=>}, "policy_number_key"=>"my_data_key"} - -Fields encrypted using a deterministic algorithm can be queried. Only exact match -queries are supported. For more details please consult `the server documentation -`_. - -.. code-block:: ruby - - # We can find documents by deterministically encrypted fields. - Patient.where(blood_type: "AB+").to_a - # => [#] - -Encryption Key Management -========================= - -Customer Master Keys -~~~~~~~~~~~~~~~~~~~~ - -Your Customer Master Key is the key you use to encrypt your Data Encryption Keys. -MongoDB automatically encrypts Data Encryption Keys using the specified CMK -during Data Encryption Key creation. - -The CMK is the most sensitive key in CSFLE. If your CMK is compromised, all of -your encrypted data can be decrypted. - -.. important:: - Ensure you store your Customer Master Key (CMK) on a remote KMS. - - To learn more about why you should use a remote KMS, see `Reasons to Use a Remote KMS. `_ - - To view a list of all supported KMS providers, see the `KMS Providers `_ page. - -MongoDB CSFLE supports the following Key Management System (KMS) providers: - * `Amazon Web Services KMS `_ - * `Azure Key Vault `_ - * `Google Cloud Platform KMS `_ - * Any KMIP Compliant Key Management System - * Local Key Provider (for testing only) - -Data Encryption Keys -~~~~~~~~~~~~~~~~~~~~ - -Data Encryption Keys can be created using the -``db:mongoid:encryption:create_data_key`` ``Rake`` task. By default they are -stored on the same cluster as the database. -However, it might be a good idea to store the keys separately. This can be -done by specifying a key vault client in ``mongoid.yml``: - -.. code-block:: yaml - - development: - clients: - key_vault: - uri: mongodb+srv://user:pass@anothercluster.mongodb.net/blog_development?retryWrites=true&w=majority - default: - uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority - options: - auto_encryption_options: - key_vault_client: :key_vault # Client to connect to key vault - # ... - -Encryption Keys Rotation -~~~~~~~~~~~~~~~~~~~~~~~~ - -You can rotate encryption keys using the ``rewrap_many_data_key`` method -of the Ruby driver. This method automatically decrypts multiple data encryption -keys and re-encrypts them using a specified CMK. It then updates -the rotated keys in the key vault collection. This method allows you to rotate -encryption keys based on two optional arguments: - -* A filter used to specify which keys to rotate. If no data key matches the - given filter, no keys will be rotated. Omit the filter to rotate all keys in - your key vault collection. -* An object that represents a new CMK. Omit this object to rotate the data - keys using their current CMKs. - -Here is an example of rotating keys using AWS KMS: - -.. code-block:: ruby - - # Create a key vault client - key_vault_client = Mongo::Client.new('mongodb+srv://user:pass@yourcluster.mongodb.net') - # Or, if you declared the key value client in mongoid.yml, use it - key_vault_client = Mongoid.client(:key_vault) - - # Create the encryption object - encryption = Mongo::ClientEncryption.new( - key_vault_client, - key_vault_namespace: 'encryption.__keyVault', - kms_providers: { - aws: { - "accessKeyId": "", - "secretAccessKey": "" - } - } - ) - - encryption.rewrap_many_data_key( - {}, # We want to rewrap all keys - { - provider: 'aws', - master_key: { - region: 'us-east-2', - key: 'arn:aws:kms:us-east-2:...' - } - } - ) - -Adding Automatic Encryption To Existing Project -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MongoDB automatic CSFLE supports encryption in place. You can enable encryption -for your existing database, and will still able to read unencrypted data. -All data written to the database will be encrypted. However, as soon as the -encryption is enabled, all query operations will use encrypted data: - -.. code-block:: ruby - - # We assume that there are two documents in the database, one created without - # encryption enabled, and one with encryption. - - # We can still read both. - Patient.all.to_a - # => - # [#, - # #] - - # But when we query, we can see only the latter one. - Patient.where(blood_type: 'AB+').to_a - # => [#] - -If you want to encrypt the existing database, it can be achieved by reading -and writing back all data, even without any changes. If you decide to do so, -please keep the following in mind: - -* Validate the integrity of existing data for consistent fidelity. CSFLE is - type sensitive - for example you cannot store integers in a field that is - declared as ``String``. -* For strings, make sure that empty values are always empty strings or just - not set, but not ``nil`` (CSFLE doesn't support native ``null``). -* This operation requires application downtime. From c2f35b72d0d843293b2091621115f095a8d1a8e2 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:38:29 -0500 Subject: [PATCH 106/113] DOCSP-42741: config pages (#91) * DOCSP-42741: config * wip * wip * some vale fixes * RM PR fixes 1 * small fix --- source/configuration.txt | 26 +- source/configuration/app-config.txt | 362 +++ .../configuration/forking-server-config.txt | 132 + source/configuration/logging-config.txt | 165 ++ .../persistence-config.txt} | 24 +- source/configuration/query-cache-config.txt | 72 + source/data-modeling.txt | 8 +- source/data-relationships/associations.txt | 23 - .../persistence-configuration.rb | 0 .../configuration/sample-config-options.yml | 235 ++ source/includes/unicode-ballot-x.rst | 1 - source/includes/unicode-checkmark.rst | 1 - source/index.txt | 3 +- .../collection-configuration.txt | 0 .../map-reduce.txt | 0 .../rails-integration.txt | 0 source/reference/associations.txt | 1655 ----------- source/reference/callbacks.txt | 120 - source/reference/configuration.txt | 1027 ------- source/reference/crud.txt | 1074 ------- source/reference/fields.txt | 1776 ------------ source/reference/indexes.txt | 279 -- source/reference/inheritance.txt | 308 -- source/reference/nested-attributes.txt | 140 - source/reference/queries.txt | 2480 ----------------- source/reference/sessions.txt | 56 - source/reference/sharding.txt | 146 - source/reference/text-search.txt | 85 - source/reference/transactions.txt | 232 -- source/reference/validation.txt | 67 - source/schema-configuration.txt | 31 - source/tutorials.txt | 17 - source/tutorials/common-errors.txt | 51 - source/whats-new.txt | 8 +- 34 files changed, 1009 insertions(+), 9595 deletions(-) create mode 100644 source/configuration/app-config.txt create mode 100644 source/configuration/forking-server-config.txt create mode 100644 source/configuration/logging-config.txt rename source/{data-modeling/persistence-configuration.txt => configuration/persistence-config.txt} (93%) create mode 100644 source/configuration/query-cache-config.txt delete mode 100644 source/data-relationships/associations.txt rename source/includes/{data-modeling => configuration}/persistence-configuration.rb (100%) create mode 100644 source/includes/configuration/sample-config-options.yml delete mode 100644 source/includes/unicode-ballot-x.rst delete mode 100644 source/includes/unicode-checkmark.rst rename source/{reference => legacy-files}/collection-configuration.txt (100%) rename source/{reference => legacy-files}/map-reduce.txt (100%) rename source/{reference => legacy-files}/rails-integration.txt (100%) delete mode 100644 source/reference/associations.txt delete mode 100644 source/reference/callbacks.txt delete mode 100644 source/reference/configuration.txt delete mode 100644 source/reference/crud.txt delete mode 100644 source/reference/fields.txt delete mode 100644 source/reference/indexes.txt delete mode 100644 source/reference/inheritance.txt delete mode 100644 source/reference/nested-attributes.txt delete mode 100644 source/reference/queries.txt delete mode 100644 source/reference/sessions.txt delete mode 100644 source/reference/sharding.txt delete mode 100644 source/reference/text-search.txt delete mode 100644 source/reference/transactions.txt delete mode 100644 source/reference/validation.txt delete mode 100644 source/schema-configuration.txt delete mode 100644 source/tutorials.txt delete mode 100644 source/tutorials/common-errors.txt diff --git a/source/configuration.txt b/source/configuration.txt index 432107ac..1c486bca 100644 --- a/source/configuration.txt +++ b/source/configuration.txt @@ -14,9 +14,31 @@ Configuration .. toctree:: :caption: Configuration + Application Configuration + Persistence Targets Sharding + Logging + Query Cache Middleware + Forking Servers + +.. Collection Configuration In this section, you can learn how to configure different options with {+odm+}. -- :ref:`Sharding Configuration `: Learn how to configure - sharding in your {+odm+} application. +- :ref:`mongoid-app-config`: Learn about settings you can use to + customize how your application works with MongoDB. + +- :ref:`mongoid-persistence`: Learn how to use {+odm+} to view and + customize your document storage. + +- :ref:`mongoid-sharding-configuration`: Learn how to configure + sharding in your application. + +- :ref:`mongoid-logging-config`: Learn how to configure loggers in your + application. + +- :ref:`mongoid-query-cache-config`: Learn how to implement query cache + middleware in your application. + +- :ref:`mongoid-forking-server-config`: Learn how to configure your + application to use a forking web server. diff --git a/source/configuration/app-config.txt b/source/configuration/app-config.txt new file mode 100644 index 00000000..05ad16ea --- /dev/null +++ b/source/configuration/app-config.txt @@ -0,0 +1,362 @@ +.. _mongoid-app-config: + +========================= +Application Configuration +========================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, customize, behavior + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to configure how your application +connects to MongoDB and how it processes your data. When you set up your +application, you are required to supply a **connection string**, or +connection URI, which contains a set of instructions that {+odm+} uses to +connect to a MongoDB. To learn more about connection strings, see +:manual:`Connection Strings ` in the +{+server-manual+}. + +You primarily configure {+odm+} by using a ``mongoid.yml`` file that +specifies your connection options and clients. To learn more about +creating a ``mongoid.yml`` file when setting up an application, see one +of the following guides: + +- :ref:`mongoid-quick-start-rails` +- :ref:`mongoid-quick-start-sinatra` + +Structure of mongoid.yml +------------------------ + +The simplest configuration for an application configures {+odm+} to +connect to a MongoDB server at ``localhost:27017`` and access the database +named ``myDB``. The ``mongoid.yml`` file for this configuration +resembles the following file: + +.. code-block:: yaml + :caption: mongoid.yml + + development: + clients: + default: + database: myDB + hosts: + - localhost:27017 + +The top level key in the preceding file, ``development``, refers to the +environment name which the application is running. Possible values include +``development``, ``test`` or ``production``. The third level key, +``default``, refers to the MongoDB client name. Most applications use a +single client named ``default``. + +Generate Default Configuration +------------------------------ + +If you are using {+ror+} as your application framework, you can generate +a default configuration file in your application by running the +following shell command: + +.. code-block:: bash + + rails g mongoid:config + +This command creates the configuration file at ``config/mongoid.yml``. +An initializer is also created at ``config/initializers/mongoid.rb``. We +recommended that you specify all settings in ``mongoid.yml``, but you can +also use the ``mongoid.rb`` initializer to set configuration options. +Settings in ``mongoid.yml`` always override settings in the initializer. + +If you are not using {+ror+}, you can create the ``config/mongoid.yml`` +file, then copy and paste the configuration file shown in the preceding +section. + +Load {+odm+} Configuration +-------------------------- + +.. important:: + + You must configure {+odm+} before using any {+odm+} component. After you + use or reference a component, changing the configuration might not apply + changes to already instantiated components. + +If you are using {+ror+}, Rails automatically loads your {+odm+} +configuration for the current environment as stored in ``Rails.env``. + +You can also specify {+odm+} as the ORM for your application by +adding the following lines to your ``application.rb`` file: + +.. code-block:: ruby + + config.generators do |g| + g.orm :mongoid + end + +If you are not using {+ror+}, you must load your {+odm+} configuration +manually. Call the ``Mongoid.load!`` method, which takes the +configuration file path as its argument: + +.. code-block:: ruby + + # Uses automatically detected environment name + Mongoid.load!("config/mongoid.yml") + + # Specifies environment name manually + Mongoid.load!("config/mongoid.yml", :development) + +{+odm+} detects the environment name by examining the following sources, +in order: + +1. If ``Rails`` top-level constant is defined: ``Rails.env`` +#. If ``Sinatra`` top-level constant is defined: ``Sinatra::Base.environment`` +#. ``RACK_ENV`` environment variable +#. ``MONGOID_ENV`` environment variable. + +.. note:: + + You can also configure {+odm+} directly in {+language+} without using + a configuration file. This configuration style does not support the + concept of environments. Any configuration that you provide is + applied to the current environment. + +.. _mongoid-config-options-all: + +Configuration Options +--------------------- + +The following annotated example ``mongoid.yml`` file describes where to +set each type of configuration option and provides information about +each option and its parameters. + +.. literalinclude:: /includes/configuration/sample-config-options.yml + :language: yaml + +Version Based Defaults +---------------------- + +{+odm+} supports setting configuration options to their default values +for specific versions. This might be useful for when you update to a new +{+odm+} version. When upgrading your {+odm+} version, set the following +options on ``Mongoid::Config``: + +.. code-block:: ruby + + Mongoid.configure do |config| + config.load_defaults + end + +This allows you to upgrade to a new version of {+odm+} while using +the configuration options from the previous installed version. +This feature gives you more time to implement tests for each changed +behavior to make sure your code doesn't break or behave in unexpected +ways. After you verify that your application works as expected, you can +remove this code to use the current version's default configuration. + +ERb Pre-processing +------------------ + +When loading a configuration file, {+odm+} processes it by using +:github:`ERb ` before parsing it as YAML. This allows {+odm+} +to construct the contents of the configuration file at runtime +based on environment variables. + +Because {+odm+} performs ERb rendering before YAML parsing, all ERb +directives in the configuration file are evaluated, including those +occurring in YAML comments. + +The following sample ``mongoid.yml`` file demonstrates how you can +reference an environment variable that stores your connection string: + +.. code-block:: yaml + + development: + clients: + default: + uri: "<%= ENV['MONGODB_URI'] %>" + +.. tip:: + + When outputting values from ERb, ensure the values are valid YAML + and escape them as needed. + +.. _mongoid-config-time-zones: + +Time Zone Configuration +----------------------- + +{+odm+} uses Active Support's time zone functionality, which provides +more functionality than {+language+}'s standard library. Active Support +allows configuration of the ``Time.zone`` variable, a thread-global +variable which provides context for working with date and time values. + +You can implement correct time zone handling in your application by +performing the following steps: + +1. Set the operating system's time zone to UTC. + +#. Set your application default time zone to UTC, as shown in the + following code: + + .. code-block:: ruby + + # If using Rails, in `application.rb`: + class Application < Rails::Application + config.time_zone = 'UTC' + end + + # If not using Rails: + Time.zone = 'UTC' + +#. In each controller and job class, set the appropriate time zone in a + ``before_filter`` callback: + + .. code-block:: ruby + + class ApplicationController < ActionController::Base + before_filter :fetch_user, :set_time_zone + + def set_time_zone + Time.zone = @user.time_zone + end + end + +#. Then, you can work with times in the local time zone. + +#. Use Active Support methods instead of the {+language+} standard library. + + - Use the ``Time.zone.now`` or ``Time.current`` methods instead of + ``Time.now`` + - Use the ``Date.current`` method instead of ``Date.today`` + + The {+language+} standard library methods such as ``Time.now`` and + ``Date.today`` reference your system time zone and not the value of + the ``Time.zone`` variable. + + You might mistake these similarly named methods, so we recommend + using the `Rails/TimeZone + `__ + feature from RuboCop in your tests. + +Set Time Zone on MongoDB Data +----------------------------- + +MongoDB stores all times in UTC without time zone information. +{+odm+} models load and return time values as instances of +``ActiveSupport::TimeWithZone``. You can set the ``use_utc`` option +to control how {+odm+} sets the time zone when loading values from the +database: + +- ``false`` (*default*): {+odm+} uses the value of ``Time.zone`` to set + the time zone of values from the database. +- ``true``: {+odm+} always sets the time zone as UTC on loaded + time values. + +The ``use_utc`` option affects only how data is loaded and does not affect +how data is persisted. For example, if you assign a ``Time`` or +``ActiveSupport::TimeWithZone`` instance to a time field, the time +zone information of the assigned instance is used +regardless of the ``use_utc`` value. + +Alternatively, if you assign a string value to a time field, any time +zone information in the string is parsed. If the string does not include +time zone information it is parsed according to the ``Time.zone`` value. + +The following code sets a ``Time.zone`` value and demonstrates how +{+odm+} processes different time strings: + +.. code-block:: ruby + + Time.use_zone("Asia/Kolkata") do + + # String does not include time zone, so "Asia/Kolkata" is used + ghandi.born_at = "1869-10-02 7:10 PM" + + # Time zone in string (-0600) is used + amelia.born_at = "1897-07-24 11:30 -0600" + end + +SSL/TLS Configuration +--------------------- + +You can configure advanced TLS options in your application, such as +enabling or disabling certain ciphers. To learn about the main SSL/TLS +options, see the :ref:`mongoid-config-options-all` section of this guide. + +You can set TLS context hooks on the {+ruby-driver+}. TLS context +hooks are user-provided ``Proc`` objects that are invoked before any TLS +socket connection is created in the driver. These hooks can be used to +modify the underlying ``OpenSSL::SSL::SSLContext`` object used by the +socket. + +To set TLS context hooks, add a ``Proc`` to the ``Mongo.tls_context_hooks`` +array. This task can be done in an initializer. The following example adds a hook +that only enables the ``"AES256-SHA"`` cipher: + +.. code-block:: ruby + + Mongo.tls_context_hooks.push( + Proc.new { |context| + context.ciphers = ["AES256-SHA"] + } + ) + +Every ``Proc`` in ``Mongo.tls_context_hooks`` is passed an +``OpenSSL::SSL::SSLContext`` object as its sole argument. These ``Proc`` +objects are run sequentially during socket creation. + +.. warning:: + + TLS context hooks are global and affect all ``Mongo::Client`` + instances in an application. + +To learn more about TLS context hooks, see :ruby:`Modifying SSLContext +` in the {+ruby-driver+} +documentation. + +Network Compression +------------------- + +{+odm+} supports compression of messages to and from MongoDB. This +functionality is provided by the {+ruby-driver+}, which implements the +following supported algorithms: + +- `Zstandard `__ (*Recommended*): To use ``zstandard`` + compression, you must install the `zstd-ruby + `__ library. This compressor + produces the highest compression at the same CPU consumption compared + to the other compressors. +- :github:`Snappy `: To use ``snappy`` compression, you + must install the `snappy `__ library. +- `Zlib `__: To use ``zlib`` compression, you + must install the `zlib `__ library. + +To use wire protocol compression, configure the driver options +in your ``mongoid.yml`` file: + +.. code-block:: yaml + + development: + clients: + default: + ... + options: + # Specify compresses to use. (default is no compression) + # Accepted values are zstd, zlib, snappy, or a combination + compressors: ["zstd", "snappy"] + +If you do not explicitly request any compressors, the driver does not +use compression, even if the required dependencies for one or more +compressors are installed. + +The driver chooses the first compressor, if you specify multiple, that +is supported by the MongoDB Server. diff --git a/source/configuration/forking-server-config.txt b/source/configuration/forking-server-config.txt new file mode 100644 index 00000000..ea896857 --- /dev/null +++ b/source/configuration/forking-server-config.txt @@ -0,0 +1,132 @@ +.. _mongoid-forking-server-config: + +============================ +Forking Server Configuration +============================ + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, parent process, child process, behavior + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn about configuring your application to use a +forking web server. + +When using {+odm+} with a forking web server, adhere to the following +guidelines: + +- If possible, do not perform any MongoDB operations in the parent + process before forking. To learn more about how the {+ruby-driver+} + handles forking, see :ruby:`Usage with Forking Servers + ` in the driver + documentation. + +- You can avoid connection errors such as ``Mongo::Error::SocketError`` + and ``Mongo::Error::NoServerAvailable`` by performing the following actions: + + 1. Disconnect MongoDB clients in the parent {+language+} process + immediately *before* forking by using the + ``Mongoid.disconnect_clients`` method. This ensures that the parent and + child processes do not accidentally reuse the same sockets and have + I/O conflicts. ``Mongoid.disconnect_clients`` does not disrupt any + in-flight MongoDB operations, and automatically reconnects when you + perform new operations. + + #. Reconnect your MongoDB clients in the child {+language+} process + immediately *after* forking by using the + ``Mongoid.reconnect_clients`` method. This is required to respawn + the driver's monitoring threads in the child process. + +Most web servers provide hooks that your application can use to +perform actions when the worker processes are forked. The following +sections provide configuration examples for some common {+language+} web +servers. + +Puma +---- + +Use the ``on_worker_boot`` hook to reconnect clients in the workers. Use +the ``before_fork`` and ``on_refork`` hooks to close clients in the +parent process. To learn more about these hooks, see `Clustered mode +hooks `__ in the Puma +API documentation. + +The following code uses the ``on_worker_boot``, ``before_fork``, and +``on_refork`` hooks in a sample Puma configuration file: + +.. code-block:: ruby + :caption: config/puma.rb + + # Runs in the Puma master process before it forks a child worker. + before_fork do + 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 +------- + +Use the ``before_fork`` hook to close clients in the parent process. Use +the ``after_fork`` hook to reconnect clients in the workers. To +learn more about these hooks, see `Configurator +`__ in the Unicorn +API documentation. + +The following code uses the ``before_fork`` and ``after_fork`` +hooks in a sample Unicorn configuration file: + +.. code-block:: ruby + :caption: config/unicorn.rb + + before_fork do |_server, _worker| + Mongoid.disconnect_clients + end + + after_fork do |_server, _worker| + Mongoid.reconnect_clients + end + +Passenger +--------- + +Use the ``starting_worker_process`` hook to reconnect clients in the +workers. To learn more about this hook, see `Smart spawning hooks +`__ +in the Passenger documentation. + +.. note:: + + Passenger does have a hook that is invoked in the + parent process before the workers are forked. + +The following code uses the ``starting_worker_process`` hook to +reconnect clients: + +.. code-block:: ruby + + if defined?(PhusionPassenger) + PhusionPassenger.on_event(:starting_worker_process) do |forked| + Mongoid.reconnect_clients if forked + end + end diff --git a/source/configuration/logging-config.txt b/source/configuration/logging-config.txt new file mode 100644 index 00000000..1a6271f7 --- /dev/null +++ b/source/configuration/logging-config.txt @@ -0,0 +1,165 @@ +.. _mongoid-logging-config: + +===================== +Logging Configuration +===================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, customize, trace + +.. contents:: On this page + :local: + :backlinks: none + :depth: 3 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to configure logging in your {+odm+} +application. When configuring logging, note that {+odm+} provides a +model layer on top of the {+ruby-driver+}, and the *driver* dispatches +data operations to MongoDB. Therefore, some logging output in an +application that uses {+odm+} comes from {+odm+} itself, and some comes from +the driver. + +Driver Logger +------------- + +The {+odm+} client is a {+ruby-driver+} client instance, so the logger +of a {+odm+} client is the {+ruby-driver+} logger, not the {+odm+} +logger. The following code creates a {+odm+} client logger: + +.. code-block:: ruby + + Mongoid.client(:default).logger + +Depending on your application framework and how you configure {+odm+} +and the {+ruby-driver+}, they may use the same logger +instance or different instances, potentially with different +configurations. + +{+ror+} Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When used in a {+ror+} application, {+odm+} by default inherits +the logger and the log level from Rails. {+odm+} sets the driver's +logger to the same logger instance: + +.. code-block:: ruby + + Rails.logger === Mongoid.logger + # => true + + Mongoid.logger === Mongo::Logger.logger + # => true + +To change the log level, use the `standard Rails configuration +<{+active-record-docs+}/debugging_rails_applications.html#log-levels>`__. +Place the following block in one of your environment configuration +files, such as ``config/environments/production.rb``: + +.. code-block:: ruby + + Rails.application.configure do + config.log_level = :debug + end + +.. note:: + + The ``log_level`` {+odm+} configuration option is not used when + {+odm+} operates in a Rails application, because {+odm+} inherits + Rails' log level. + +To configure either the {+odm+} or driver logger differently from the +Rails logger, use an initializer as shown in the following code: + +.. code-block:: ruby + + Rails.application.configure do + config.after_initialize do + # Change Mongoid log destination and level + Mongoid.logger = Logger.new(STDERR).tap do |logger| + logger.level = Logger::DEBUG + end + + # Change driver log destination and level + Mongo::Logger.logger = Logger.new(STDERR).tap do |logger| + logger.level = Logger::DEBUG + end + end + end + +.. note:: + + There is no provision in the {+language+} standard library ``Logger`` + to return the log device, such as the ``IO`` object, that a logger is + using. + + To make, for example, {+odm+} or the {+ruby-driver+} log to the + standard Rails log file (``log/development.log``) with a + different level from standard Rails logger (``Rails.logger``), you + must open the file separately and pass the resulting ``IO`` object to + the ``Logger`` constructor. + +Because {+odm+} sets its own logger and the driver's logger to the +same instance as the Rails logger, modifying any of the instances affects +all the loggers. For example, the following code changes the log level for +all three loggers: + +.. code-block:: ruby + + Mongoid::Logger.logger.level = Logger::DEBUG + +Standalone Configuration +~~~~~~~~~~~~~~~~~~~~~~~~ + +When not loaded in a {+ror+} application, {+odm+} respects the +``log_level`` top-level configuration option: + +.. code-block:: yaml + :emphasize-lines: 6 + + development: + clients: + default: + ... + options: + log_level: :debug + +You can also configure the log level in-line: + +.. code-block:: ruby + + Mongoid.configure do |config| + config.log_level = :debug + end + +The default log destination is standard error. To change the log +destination, create a new logger instance as shown in the following +code: + +.. code-block:: ruby + + Mongoid.logger = Logger.new(STDERR).tap do |logger| + logger.level = Logger::DEBUG + end + +To change the {+ruby-driver+} log level or destination, add the +following block to your application file: + +.. code-block:: ruby + + Mongo::Logger.logger = Logger.new(STDERR).tap do |logger| + logger.level = Logger::DEBUG + end + +.. note:: + + {+odm+} does not change the driver's logger when running in + standalone mode. + diff --git a/source/data-modeling/persistence-configuration.txt b/source/configuration/persistence-config.txt similarity index 93% rename from source/data-modeling/persistence-configuration.txt rename to source/configuration/persistence-config.txt index 83a9f032..06414b5b 100644 --- a/source/data-modeling/persistence-configuration.txt +++ b/source/configuration/persistence-config.txt @@ -41,7 +41,7 @@ form of its representative class name. In the following example, for the ``Restaurant`` class, the corresponding collection is named ``restaurants``. For the ``Person`` class, the corresponding collection is named ``people``. -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start default modeling :end-before: end default modeling @@ -55,7 +55,7 @@ You can create a new pluralization rule for your model class by calling the instance method and passing the singular and plural forms of your class name. The following example specifies "reyes" as the plural of "rey": -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start set pluralization :end-before: end set pluralization @@ -68,7 +68,7 @@ collection. When {+odm+} stores a document in a database, it serializes the Ruby object to a BSON document that has the following structure: - .. literalinclude:: /includes/data-modeling/persistence-configuration.rb + .. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start BSON model :end-before: end BSON model @@ -88,7 +88,7 @@ database, and collection where documents for the ``Band`` class are persisted: .. io-code-block:: - .. input:: /includes/data-modeling/persistence-configuration.rb + .. input:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start persistence context attributes :end-before: end persistence context attributes @@ -120,7 +120,7 @@ to use the ``store_in`` macro to store documents from the ``Person`` class in a collection called ``citizens`` in the ``other`` database within a client named ``analytics``: -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start store_in example :end-before: end store_in example @@ -136,7 +136,7 @@ to the current thread so that users cannot access each others' data. The following example stores documents in a database determined by a thread-local variable: -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start store_in lambda example :end-before: end store_in lambda example @@ -168,7 +168,7 @@ By default, {+odm+} stores documents for the ``Band`` class in a collection call use a different client, database, and collection to perform operations on the ``Band`` class's documents: -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start with example :end-before: end with example @@ -191,7 +191,7 @@ operations. The configurations apply only to the specified type of operation. The following example uses the ``with`` method to specify the use of the secondary node for all read operations within the block. -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start read configuration :end-before: end read configuration @@ -226,7 +226,7 @@ locales in different databases. The code shows how to use the ``{+odm+}.override_database`` method to globally set the persistence context based on the locale: -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start global configuration example :end-before: end global configuration example @@ -242,7 +242,7 @@ Client and Collection Access You can access the client or collection of a model or document instance by using the ``mongo_client`` and ``collection`` class methods: -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start access client collection :end-before: end access client collection @@ -258,7 +258,7 @@ The following code example accesses the client used by the ``Band`` model class. It then uses the ``with`` method on the client to write to the ``music`` database, setting the ``w`` write option to ``0`` to not require write acknowledgement. -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start client with example :end-before: end client with example @@ -270,7 +270,7 @@ You can override the ``:read`` and ``:write`` options on a collection by using t ``with`` method. The following example shows how to use the ``with`` method to set the ``w`` write option to ``0``: -.. literalinclude:: /includes/data-modeling/persistence-configuration.rb +.. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby :start-after: start collection with example :end-before: end collection with example diff --git a/source/configuration/query-cache-config.txt b/source/configuration/query-cache-config.txt new file mode 100644 index 00000000..ec89cd39 --- /dev/null +++ b/source/configuration/query-cache-config.txt @@ -0,0 +1,72 @@ +.. _mongoid-query-cache-config: + +==================================== +Query Cache Middleware Configuration +==================================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, storage, memory + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to configure your application to use +query cache middleware. Query cache middleware allows you to +activate the :ref:`mongoid-query-cache` for each request to store your +query results. This can improve your application speed and efficiency by +reducing the number of calls your application must make to the database. + +Enable Query Cache for Rack Web Requests +---------------------------------------- + +The {+ruby-driver+} provides a Rack middleware which enables the +query cache during each web request. The +following code demonstrates how to enable the Query Cache Middleware in +a {+ror+} application: + +.. code-block:: ruby + :caption: config/application.rb + + # Add Mongo::QueryCache::Middleware at the bottom of the middleware + # stack or before other middleware that queries MongoDB. + config.middleware.use Mongo::QueryCache::Middleware + +To learn more about using Rack middleware in Rails applications, see +`Configuring Middleware Stack +<{+active-record-docs+}/rails_on_rack.html#configuring-middleware-stack>`__ +in the Rails documentation. + +Enable Query Cache for Active Job +--------------------------------- + +The {+ruby-driver+} provides Query Cache Middleware for `Active Job +<{+active-record-docs+}/active_job_basics.html>`__. +You can enable it for all jobs in an initializer, as shown in the +following code: + +.. code-block:: ruby + :caption: config/initializers/active_job.rb + + # Enable Mongo driver query cache for Active Job + ActiveSupport.on_load(:active_job) do + include Mongo::QueryCache::Middleware::ActiveJob + end + +You can also enable it for a specific job class, as shown in the +following code: + +.. code-block:: ruby + + class MyJob < ActiveJob::Base + include Mongo::QueryCache::Middleware::ActiveJob + end diff --git a/source/data-modeling.txt b/source/data-modeling.txt index 90fa06cd..7a026f86 100644 --- a/source/data-modeling.txt +++ b/source/data-modeling.txt @@ -17,12 +17,11 @@ Model Your Data Documents Field Types Field Behaviors - Persistence Configuration Inheritance Document Validation Callbacks Data Associations - Optimize Queries With Indexes + Indexes In this section, you can learn how to model data in {+odm+}. @@ -34,9 +33,6 @@ In this section, you can learn how to model data in {+odm+}. - :ref:`mongoid-field-behaviors`: Learn how to customize the behaviors of fields in {+odm+} to meet your application requirements. - -- :ref:`mongoid-persistence`: Learn how to use {+odm+} to view and customize - your document storage. - :ref:`mongoid-modeling-inheritance`: Learn how to implement inheritance in your model classes. @@ -48,7 +44,7 @@ In this section, you can learn how to model data in {+odm+}. customize the life cycle of your models. - :ref:`mongoid-associations`: Learn how to create and manage data - associations in your model classes. + associations between your model classes. - :ref:`mongoid-optimize-queries-with-indexes`: Learn how to create and manage indexes for your model classes. diff --git a/source/data-relationships/associations.txt b/source/data-relationships/associations.txt deleted file mode 100644 index a8224430..00000000 --- a/source/data-relationships/associations.txt +++ /dev/null @@ -1,23 +0,0 @@ -.. _mongoid_associations: - -============ -Associations -============ - -.. facet:: - :name: genre - :values: reference - -.. meta:: - :keywords: ruby framework, odm, code example - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Overview --------- - -Associations in {+odm+} allow you to create relationships between models. \ No newline at end of file diff --git a/source/includes/data-modeling/persistence-configuration.rb b/source/includes/configuration/persistence-configuration.rb similarity index 100% rename from source/includes/data-modeling/persistence-configuration.rb rename to source/includes/configuration/persistence-configuration.rb diff --git a/source/includes/configuration/sample-config-options.yml b/source/includes/configuration/sample-config-options.yml new file mode 100644 index 00000000..9667583a --- /dev/null +++ b/source/includes/configuration/sample-config-options.yml @@ -0,0 +1,235 @@ +development: + # Configures available database clients. (required) + clients: + # Defines the default client. (required) + default: + # Supplies your connection URI, including the database name. + uri: mongodb+srv://user:pass@mongo0.example.com/myDB + + # OR, you can define the parameters separately. + # Defines the name of the default database. (required) + database: my_db_development + # Provides the hosts the client can connect to. + # Must be an array of host:port pairs. (required) + hosts: + - localhost:27017 + options: + # All options in this section are Ruby driver client options. + # To learn more, visit + # https://www.mongodb.com/docs/ruby-driver/current/reference/create-client/ + + # Sets the write concern. (default = { w: 1 }) + write: + w: 1 + + # Sets the read preference. Valid options for mode are: :secondary, + # :secondary_preferred, :primary, :primary_preferred, :nearest + # (default: primary) + read: + mode: :secondary_preferred + tag_sets: # If using tag sets + - use: web + + # Sets name of the user for authentication. + user: "user" + + # Sets password of the user for authentication. + password: "password" + + # Sets user's database roles. + roles: + - "dbOwner" + + # Sets the authentication mechanism. Valid options include: + # :scram, :scram256, :mongodb_cr, :mongodb_x509, :gssapi, :aws, :plain + # MongoDB Server defaults to :scram + auth_mech: :scram + + # Sets the database or source to authenticate the user against. + # (default: the database specified above or admin) + auth_source: admin + + # Specifies type of connection. Can be one of: :direct, + # :replica_set, :sharded + # Set to :direct when connecting to hidden members of a replica set. + connect: :direct + + # Changes the time taken for the server monitors to refresh + # their status via hello commands. (default: 10) + heartbeat_frequency: 10 + + # Sets time in seconds for selecting servers for a :nearest read + # preference. (default: 0.015) + local_threshold: 0.015 + + # Sets timeout in seconds for selecting a server for an + # operation. (default: 30) + server_selection_timeout: 30 + + # Sets maximum number of connections in the connection pool. + # (default: 5) + max_pool_size: 5 + + # Sets minimum number of connections in the connection pool. + # (default: 1) + min_pool_size: 1 + + # Sets time to wait, in seconds, in the connection pool for a + # connection to be checked in before timing out. (default: 5) + wait_queue_timeout: 5 + + # Sets time to wait to establish a connection before timing out, + # in seconds. (default: 10) + connect_timeout: 10 + + # Sets name of the replica set to connect to. Servers provided as + # seeds that do not belong to this replica set are ignored. + replica_set: myRS + + # Sets compressors to use for wire protocol compression. + # (default is to not use compression) + compressors: ["zstd", "snappy", "zlib"] + + # Specified whether to connect to the servers by using ssl. + # (default: false) + ssl: true + + # Sets certificate file used to identify the connection for SSL. + ssl_cert: /path/to/my.cert + + # Sets private keyfile used to identify the connection against MongoDB. + # Even if the key is stored in the same file as the certificate, + # both need to be explicitly specified. + ssl_key: /path/to/my.key + + # Sets passphrase for the private key. + ssl_key_pass_phrase: password123 + + # Specifies whether to do peer certification validation. + # (default: true) + ssl_verify: true + + # Sets file containing concatenated certificate authority + # certificates used to validate certs passed from the other end + # of the connection. + ssl_ca_cert: /path/to/ca.cert + + # Specifies whether to truncate long log lines. (default: true) + truncate_logs: true + + # Optional Mongoid-specific configuration. + options: + # Allows BSON::Decimal128 to be parsed and returned directly in + # field values. Only has effect when BSON 5+ is present. + allow_bson5_decimal128: false + + # Sets app name that is printed to the MongoDB logs upon establishing + # a connection. Value is used as the database name if the database + # name is not provided. (default: nil) + app_name: nil + + # When false, callbacks for embedded documents will not be + # called. This is the default in 9.0. + # Setting this flag to true restores the pre-9.0 behavior, where callbacks + # for embedded documents are called, which might lead to stack overflow errors. + around_callbacks_for_embeds: false + + # Sets the async_query_executor for the application. By default the + # thread pool executor is set to :immediate. Options are: + # - :immediate - Initializes a single +Concurrent::ImmediateExecutor+ + # - :global_thread_pool - Initializes a single +Concurrent::ThreadPoolExecutor+ + # that uses the +async_query_concurrency+ for the +max_threads+ value. + async_query_executor: :immediate + + # Marks belongs_to associations as required by default, so saving a + # model with a missing association triggers a validation error. + belongs_to_required_by_default: true + + # Sets the global discriminator key. + discriminator_key: "_type" + + # Raises an exception when a field is redefined. + duplicate_fields_exception: false + + # Defines how many asynchronous queries can be executed concurrently. + # This option should be set only if `async_query_executor` is set + # to `:global_thread_pool`. + global_executor_concurrency: nil + + # When this flag is true, any attempt to change the _id of a persisted + # document will raise an exception (Errors::ImmutableAttribute). + # This is the default in 9.0. Setting this flag to false restores the + # pre-9.0 behavior, where changing the _id of a persisted + # document might be ignored. + immutable_ids: true + + # Includes the root model name in json serialization. + include_root_in_json: false + + # # Include the _type field in serialization. + include_type_for_serialization: false + + # Specifies whether to join nested persistence contexts for atomic + # operations to parent contexts. + join_contexts: false + + # When this flag is false (the default for 9.0), a document that + # is created or loaded remembers the storage options that were active + # when it was loaded, and will use those same options by default when + # saving or reloading itself. + # + # When this flag is true, a document does not remember the storage + # options from when it was loaded/created, and + # subsequent updates will need to explicitly set up those options + # each time. + legacy_persistence_context_behavior: false + + # Specifies whether to use legacy read-only behavior. To learn more, + # visit https://www.mongodb.com/docs/mongoid/current/interact-data/crud + legacy_readonly: false + + # Sets the log level. This must be set before referencing clients + # or Mongo.logger, because changes to this option are not be + # propagated to any clients and loggers that already exist. + log_level: :info + + # Stores BigDecimals as Decimal128s instead of strings in the db. + map_big_decimal_to_decimal128: true + + # Preloads all models in development, needed when models use + # inheritance. + preload_models: false + + # When this flag is true, callbacks for every embedded document will be + # called only once, even if the embedded document is embedded in multiple + # documents in the root document's dependencies graph. + # This is the default in 9.0. Setting this flag to false restores the + # pre-9.0 behavior, where callbacks are called for every occurrence of an + # embedded document. + prevent_multiple_calls_of_embedded_callbacks: true + + # Raises an error when performing a find operation and no document + # matches. + raise_not_found_error: true + + # Raises an error when defining a scope with the same name as an + # existing method. + scope_overwrite_exception: false + + # Returns stored times as UTC. + use_utc: false + + # Optional driver-specific configuration. + driver_options: + # When this flag is off, an aggregation done on a view is performed on + # the documents included in that view, instead of all documents in the + # 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 is correctly + # propagated to readable methods. + broken_view_options: true + + # When this flag is set to true, the update and replace methods + # validate the parameters and raise an error if they are invalid. + validate_update_replace: false diff --git a/source/includes/unicode-ballot-x.rst b/source/includes/unicode-ballot-x.rst deleted file mode 100644 index 50c3667a..00000000 --- a/source/includes/unicode-ballot-x.rst +++ /dev/null @@ -1 +0,0 @@ -.. |x| unicode:: U+2717 diff --git a/source/includes/unicode-checkmark.rst b/source/includes/unicode-checkmark.rst deleted file mode 100644 index 16f4e947..00000000 --- a/source/includes/unicode-checkmark.rst +++ /dev/null @@ -1 +0,0 @@ -.. |checkmark| unicode:: U+2713 diff --git a/source/index.txt b/source/index.txt index ae3e2931..cca00495 100644 --- a/source/index.txt +++ b/source/index.txt @@ -15,11 +15,10 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Quick Start - {+ror+} Quick Start - Sinatra Add {+odm+} to an Existing Application + Configuration Interact with Data Model Your Data - Configuration Secure Your Data - /working-with-data API Documentation What's New Compatibility diff --git a/source/reference/collection-configuration.txt b/source/legacy-files/collection-configuration.txt similarity index 100% rename from source/reference/collection-configuration.txt rename to source/legacy-files/collection-configuration.txt diff --git a/source/reference/map-reduce.txt b/source/legacy-files/map-reduce.txt similarity index 100% rename from source/reference/map-reduce.txt rename to source/legacy-files/map-reduce.txt diff --git a/source/reference/rails-integration.txt b/source/legacy-files/rails-integration.txt similarity index 100% rename from source/reference/rails-integration.txt rename to source/legacy-files/rails-integration.txt diff --git a/source/reference/associations.txt b/source/reference/associations.txt deleted file mode 100644 index 46d4c880..00000000 --- a/source/reference/associations.txt +++ /dev/null @@ -1,1655 +0,0 @@ -.. _associations: - -************ -Associations -************ - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Referenced Associations -======================= - -Mongoid supports the ``has_one``, ``has_many``, ``belongs_to`` and -``has_and_belongs_to_many`` associations familiar to ActiveRecord users. - - -Has One -------- - -Use the ``has_one`` macro to declare that the parent has a child stored in -a separate collection. The child is optional by default: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_one :studio - end - -When using ``has_one``, the child model must use ``belongs_to`` to declare the -association with the parent: - -.. code-block:: ruby - - class Studio - include Mongoid::Document - - belongs_to :band - end - -Given the above definitions, every child document contains a reference to -its respective parent document: - -.. code-block:: ruby - - band = Band.create!(studio: Studio.new) - # => # - - band.studio - # => # - -Use validations to require that the child is present: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_one :studio - - validates_presence_of :studio - end - - -Has Many --------- - -Use the ``has_many`` association to declare that the parent has zero or more -children stored in a separate collection: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_many :members - end - -Like with ``has_one``, the child model must use ``belongs_to`` to declare the -association with the parent: - -.. code-block:: ruby - - class Member - include Mongoid::Document - - belongs_to :band - end - -Also as with ``has_one``, the child documents contain references to their -respective parents: - -.. code-block:: ruby - - band = Band.create!(members: [Member.new]) - # => # - - band.members - # => [#] - -Use validations to require that at least one child is present: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_many :members - - validates_presence_of :members - end - - -Queries -``````` - -.. _has-many-any: - -``any?`` -~~~~~~~~ - -Use the ``any?`` method on the association to efficiently determine whether -the association contains any documents, without retrieving the entire set -of documents from the database: - -.. code-block:: ruby - - band = Band.first - band.members.any? - -``any?`` also implements the `Enumerable#any? API -`_, allowing -filtering with a block: - -.. code-block:: ruby - - band = Band.first - band.members.any? { |member| member.instrument == 'piano' } - -... or by a class name which can be useful for polymorphic associations: - -.. code-block:: ruby - - class Drummer < Member - end - - band = Band.first - band.members.any?(Drummer) - -If the association is already loaded, ``any?`` inspects the loaded -documents and does not query the database: - -.. code-block:: ruby - - band = Band.first - # Queries the database - band.members.any? - - band.members.to_a - - # Does not query the database - band.members.any? - -Note that simply calling ``any?`` would *not* load the association -(since ``any?`` only retrieves the _id field of the first matching document). - -``exists?`` -~~~~~~~~~~~ - -The ``exists?`` method on the association determines whether there are -any *persisted* documents in the association. Unlike the ``any?`` method: - -- ``exists?`` always queries the database, even if the association is already - loaded. -- ``exists?`` does not consider non-persisted documents. -- ``exists?`` does not allow filtering in the application like ``any?`` does, - and does not take any arguments. - -The following example illustrates the difference between ``exists?`` and -``any?``: - -.. code-block:: ruby - - band = Band.create! - # Member is not persisted. - band.members.build - - band.members.any? - # => true - band.members.exists? - # => false - - # Persist the member. - band.members.map(&:save!) - - band.members.any? - # => true - band.members.exists? - # => true - - -Belongs To ----------- - -Use the ``belongs_to`` macro to associate a child with a parent stored in a -separate collection. The ``_id`` of the parent (if a parent is associated) -is stored in the child. - -By default, if a ``belongs_to`` association is defined on a model, it must be -provided a value for a model instance to be saved. Use the ``optional: true``` -option to make the instances persistable without specifying the parent: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_one :studio - end - - class Studio - include Mongoid::Document - - belongs_to :band, optional: true - end - - studio = Studio.create! - # => # - -To change the default behavior of ``belongs_to`` associations to not require -their respective parents globally, set the ``belongs_to_required_by_default`` -:ref:`configuration option ` to ``false``. - -Although ``has_one`` and ``has_many`` associations require the -corresponding ``belongs_to`` association to be defined on the child, -``belongs_to`` may also be used without a corresponding ``has_one`` or -``has_many`` macro. In this case the child is not accessible from the parent -but the parent is accessible from the child: - -.. code-block:: ruby - - class Band - include Mongoid::Document - end - - class Studio - include Mongoid::Document - - belongs_to :band - end - -For clarity it is possible to add the ``inverse_of: nil`` option in cases when -the parent does not define the association: - -.. code-block:: ruby - - class Band - include Mongoid::Document - end - - class Studio - include Mongoid::Document - - belongs_to :band, inverse_of: nil - end - - -Has And Belongs To Many ------------------------ - -Use the ``has_and_belongs_to_many`` macro to declare a many-to-many -association: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_and_belongs_to_many :tags - end - - class Tag - include Mongoid::Document - - has_and_belongs_to_many :bands - end - -Both model instances store a list of ids of the associated models, if any: - -.. code-block:: ruby - - band = Band.create!(tags: [Tag.create!]) - # => # - - band.tags - # => [#] - -You can create a one-sided ``has_and_belongs_to_many`` association to store -the ids only in one document using the ``inverse_of: nil`` option: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_and_belongs_to_many :tags, inverse_of: nil - end - - class Tag - include Mongoid::Document - end - - band = Band.create!(tags: [Tag.create!]) - # => # - - band.tags - # => [#] - -A one-sided ``has_and_belongs_to_many`` association is, naturally, only -usable from the model where it is defined. - -.. note:: - - Given two models, A and B where A ``has_and_belongs_to_many`` B, - when adding a document of type B to the HABTM association on a document of - type A, Mongoid will not update the ``updated_at`` field for the document of - type A, but will update the ``updated_at`` field for the document of type B. - -Querying Referenced Associations --------------------------------- - -In most cases, efficient queries across referenced associations (and in general -involving data or conditions or multiple collections) are performed using -the aggregation pipeline. Mongoid helpers for constructing aggregation pipeline -queries are described in the :ref:`aggregation pipeline ` -section. - -For simple queries, the use of aggregation pipeline may be avoided and -associations may be queried directly. When querying associations directly, -all conditions must be on that association's collection only (which typically -means association in question and any associations embedded in it). - -For example, given the following models: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_many :tours - has_many :awards - - field :name, type: String - end - - class Tour - include Mongoid::Document - - belongs_to :band - - field :year, type: Integer - end - - class Award - include Mongoid::Document - - belongs_to :band - - field :name, type: String - end - -One could retrieve all bands that have toured since 2000 as follows: - -.. code-block:: ruby - - band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id) - bands = Band.find(band_ids) - -The conditions on ``Tour`` can be arbitrarily complex, but they must all -be on the same ``Tour`` document (or documents embedded in ``Tour``). - -To find awards for bands that have toured since 2000: - -.. code-block:: ruby - - band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id) - awards = Award.where(band_id: {'$in' => band_ids}) - - -Embedded Associations -===================== - -Thanks to MongoDB's document model, Mongoid also offers embedded associations -which allow documents of different types to be stored hierarchically -in the same collection. Embedded associations are defined using -``embeds_one``, ``embeds_many`` and ``embedded_in`` macros, plus -``recursively_embeds_one`` and ``recursively_embeds_many`` for recursive -embedding. - -Embeds One ----------- - -One to one associations where the children are embedded in the parent -document are defined using Mongoid's ``embeds_one`` and ``embedded_in`` macros. - -Defining -```````` - -The parent document of the association should use the ``embeds_one`` macro to -indicate is has one embedded child, where the document that is embedded uses -``embedded_in``. Definitions are required on both sides to the association -in order for it to work properly. - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_one :label - end - - class Label - include Mongoid::Document - field :name, type: String - embedded_in :band - end - -Storage -``````` - -Documents that are embedded using the ``embeds_one`` macro are stored as a -hash inside the parent in the parent's database collection. - -.. code-block:: ruby - - { - "_id" : ObjectId("4d3ed089fb60ab534684b7e9"), - "label" : { - "_id" : ObjectId("4d3ed089fb60ab534684b7e0"), - "name" : "Mute", - } - } - -You can optionally tell Mongoid to store the embedded document in a different -attribute other than the name, by providing the ``:store_as`` option. - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_one :label, store_as: "lab" - end - - -Embeds Many ------------ - -One to many relationships where the children are embedded in the parent -document are defined using Mongoid's ``embeds_many`` and ``embedded_in`` macros. - -Defining -```````` - -The parent document of the association should use the ``embeds_many`` macro -to indicate it has many embedded children, where the document that is -embedded uses ``embedded_in``. Definitions are required on both sides of -the association in order for it to work properly. - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_many :albums - end - - class Album - include Mongoid::Document - field :name, type: String - embedded_in :band - end - -Storage -``````` - -Documents that are embedded using the ``embeds_many`` macro are stored as -an array of hashes inside the parent in the parent's database collection. - -.. code-block:: ruby - - { - "_id" : ObjectId("4d3ed089fb60ab534684b7e9"), - "albums" : [ - { - "_id" : ObjectId("4d3ed089fb60ab534684b7e0"), - "name" : "Violator", - } - ] - } - -You can optionally tell Mongoid to store the embedded document in a different -attribute other than the name, by providing the ``:store_as`` option. - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_many :albums, store_as: "albs" - end - -Recursive Embedding -------------------- - -A document can recursively embed itself using ``recursively_embeds_one`` or -``recursively_embeds_many``, which provides accessors for the parent and -children via ``parent_`` and ``child_`` methods. - -.. code-block:: ruby - - class Tag - include Mongoid::Document - field :name, type: String - recursively_embeds_many - end - - root = Tag.new(name: "programming") - child_one = root.child_tags.build - child_two = root.child_tags.build - - root.child_tags # [ child_one, child_two ] - child_one.parent_tag # [ root ] - child_two.parent_tag # [ root ] - - class Node - include Mongoid::Document - recursively_embeds_one - end - - root = Node.new - child = Node.new - root.child_node = child - - root.child_node # child - child.parent_node # root - -Referencing Vs Embedding ------------------------- - -While a complete discussion of referencing vs embedding is beyond the scope -of this tutorial, here are some high level considerations for choosing -one over the other. - -When an association is embedded, both parent and child documents are stored -in the same collection. This permits efficient persistence and retrieval -when both are used/needed. For example, if the navigation bar on a web site -shows attributes of a user that are stored in documents themselves, it is -often a good idea to use embedded associations. - -Using embedded associations allows using MongoDB tools like the -`aggregation pipeline -`_ to query -these documents in a powerful way. - -Because embedded documents are stored as part of their parent top-level -documents, it is not possible to persist an embedded document by itself, -nor is it possible to retrieve embedded documents directly. However, -embedded documents can still be efficiently queried and retrieved with the -help of MongoDB projection operation: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :started_on, type: Date - embeds_one :label - end - - class Label - include Mongoid::Document - field :name, type: String - embedded_in :band - end - - # Retrieve labels for bands started in the last year. - # - # Sends a find query like this: - # {"find"=>"bands", - # "filter"=>{"started_on"=>{"$gt"=>2018-07-01 00:00:00 UTC}}, - # "projection"=>{"_id"=>1, "label"=>1}} - Band.where(started_on: {'$gt' => Time.now - 1.year}).only(:label).map(&:label).compact.uniq - -Setting Stale Values on Referenced Associations -``````````````````````````````````````````````` - -Setting a stale value to a referenced association can sometimes result in -a ``nil`` value being persisted to the database. Take the following case: - -.. code-block:: ruby - - class Post - include Mongoid::Document - - has_one :comment, inverse_of: :post - end - - class Comment - include Mongoid::Document - - belongs_to :post, inverse_of: :comment, optional: true - end - - post.comment = comment1 - post.reload - -At this point, ``post.comment`` is set to ``comment1``, however since a reload -happened, ``post.comment`` does not refer to the same object as ``comment1``. -Meaning, updating one object does not implicitly update the other. This matters -for the next operation: - -.. code-block:: ruby - - post.comment = comment2 - post.reload - -Now, ``post.comment`` is set to ``comment2``, and the ``post_id`` of the old -comment is set to ``nil``. However, the value that was assigned to -``post.comment`` did not refer to the same object as ``comment1``, therefore, -while the old value of ``post.comment`` was updated to have a ``nil`` -``post_id``, ``comment1`` still has the ``post_id`` set. - -.. code-block:: ruby - - post.comment = comment1 - post.reload - -Finally, this last assignment attempts to set the ``post_id`` on ``comment1``, -which should be ``nil`` at this point, but is set to the old ``post_id``. -During this operation, the ``post_id`` is cleared from ``comment2``, and the -new ``post_id`` is set on ``comment1``. However, since the ``post_id`` was -already set on ``comment1``, nothing is persisted, and we end up with both -comments having a ``nil`` ``post_id``. At this point, running ``post.comment`` -returns ``nil``. - - -Querying Embedded Associations ------------------------------- - -When querying top-level documents, conditions can be specified on documents -in embedded associations using the dot notation. For example, given the -following models: - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_many :tours - embeds_many :awards - field :name, type: String - end - - class Tour - include Mongoid::Document - embedded_in :band - field :year, type: Integer - end - - class Award - include Mongoid::Document - embedded_in :band - field :name, type: String - end - -To retrieve bands based on tour attributes, use the dot notation as follows: - -.. code-block:: ruby - - # Get all bands that have toured since 2000 - Band.where('tours.year' => {'$gte' => 2000}) - -To retrieve only documents of embedded associations, without retrieving -top-level documents, use the ``pluck`` projection method: - -.. code-block:: ruby - - # Get awards for bands that have toured since 2000 - Band.where('tours.year' => {'$gte' => 2000}).pluck(:awards) - -.. _embedded-matching: - -Querying Loaded Associations -```````````````````````````` - -Mongoid query methods can be used on embedded associations of documents which -are already loaded in the application. This mechanism is called -"embedded matching" and it is implemented entirely in Mongoid--the queries -are NOT sent to the server. - -The following operators are supported: - -- :manual:`Comparison operators ` -- :manual:`Logical operators ` -- :manual:`Array query operators ` -- :manual:`$exists ` -- :manual:`$mod ` -- :manual:`$type ` -- :manual:`$regex ` (``$options`` field - is only supported when the ``$regex`` argument is a string) -- :manual:`Bitwise operators ` -- :manual:`$comment ` - -For example, using the model definitions just given, we could query -tours on a loaded band: - -.. code-block:: ruby - - band = Band.where(name: 'Astral Projection').first - tours = band.tours.where(year: {'$gte' => 2000}) - -Embedded Matching vs Server Behavior -```````````````````````````````````` - -Mongoid's embedded matching aims to support the same functionality and -semantics as native queries on the latest MongoDB server version. -Note the following known limitations: - -- Embedded matching is not implemented for :ref:`text search `, - :manual:`geospatial query operators `, - operators that execute JavaScript code (:manual:`$where `) - and operators that are implemented via other server functionality such as - :manual:`$expr ` - and :manual:`$jsonSchema `. - -- Mongoid DSL expands ``Range`` arguments to hashes with ``$gte`` and ``$lte`` - conditions. `In some cases `_ - this creates bogus queries. Embedded matchers raise the ``InvalidQuery`` - exception in these cases. The operators that are known to be affected are - ``$elemMatch``, ``$eq``, ``$gt``, ``$gte``, ``$lt``, ``$lte`` and ``$ne``. - -- When performing embedded matching with ``$regex``, `it is not currently - possible `_ to specify a - regular expression object as the pattern and also provide options. - -- MongoDB Server 4.0 and earlier servers do not validate ``$type`` arguments strictly - (for example, allowing invalid arguments like 0). This is validated more strictly on - the client side. - - -.. _omit-id: - -Omitting ``_id`` Fields ------------------------ - -By default, Mongoid adds an ``_id`` field to each embedded document. This -permits easy referencing of and operations on the embedded documents. - -These ``_id`` fields may be omitted to save storage space. To do so, -:ref:`override the _id field definition in the child documents ` -and remove the default value: - -.. code-block:: ruby - - class Order - include Mongoid::Document - - embeds_many :line_items - end - - class LineItem - include Mongoid::Document - - embedded_in :order - - field :_id, type: Object - end - -In the current version of Mongoid the field definition is required, but -without a default value specified no value will be stored in the database. -A future version of Mongoid may allow removing previously defined fields. - -.. note:: - - Removing the ``_id`` field means that embedded documents must be identified - by their content attribute values during queries, updates and deletes. - - -Deleting --------- - -Mongoid provides three methods for deleting children from ``embeds_many`` -associations: ``clear``, ``destroy_all`` and ``delete_all``. - -``clear`` -````````` - -The ``clear`` method uses the :manual:`$unset operator -` to remove the entire association from the -host document. It does not run destroy callbacks on the documents being removed, -acting like ``delete_all`` in this regard: - -.. code-block:: ruby - - band = Band.find(...) - band.tours.clear - -If ``clear`` is called on an association in an unsaved host document, it will -still try to remove the association from the database based on the host -document's ``_id``: - -.. code-block:: ruby - - band = Band.find(...) - band.tours << Tour.new(...) - - unsaved_band = Band.new(id: band.id, tours: [Tour.new]) - # Removes all tours from the persisted band due to _id match. - unsaved_band.tours.clear - - band.tours - # => [] - -``delete_all`` -`````````````` - -The ``delete_all`` method removes the documents that are in the association -using the :manual:`$pullAll operator `. -Unlike ``clear``, ``delete_all``: - -- Loads the association, if it wasn't yet loaded; -- Only removes the documents that exist in the application. - -``delete_all`` does not run destroy callbacks on the documents being removed. - -Example: - -.. code-block:: ruby - - band = Band.find(...) - band.tours.delete_all - - -``destroy_all`` -``````````````` - -The ``delete_all`` method removes the documents that are in the association -using the :manual:`$pullAll operator ` -while running the destroy callbacks. Like ``delete_all``, ``destroy_all`` -loads the entire association if it wasn't yet loaded and it only removes -documents that exist in the application: - -.. code-block:: ruby - - band = Band.find(...) - band.tours.destroy_all - - -.. _hash-assignment: - -Hash Assignment ---------------- - -Embedded associations allow the user to assign a ``Hash`` instead of a document -to an association. On assignment, this hash is coerced into a document of the -class of the association that it's being assigned to. Take the following -example: - -.. code:: ruby - - class Band - include Mongoid::Document - embeds_many :albums - end - - class Album - include Mongoid::Document - field :name, type: String - embedded_in :band - end - - band = Band.create! - band.albums = [ { name: "Narrow Stairs" }, { name: "Transatlanticism" } ] - p band.albums - # => [ #, # ] - -This works for ``embeds_one``, ``embeds_many``, and ``embedded_in`` associations. -Note that you cannot assign hashes to referenced associations. - - -Common Behavior -=============== - -Extensions ----------- - -All associations can have extensions, which provides a way to add application specific -functionality to the association. They are defined by providing a block to the association definition. - -.. code-block:: ruby - - class Person - include Mongoid::Document - embeds_many :addresses do - def find_by_country(country) - where(country: country).first - end - def chinese - _target.select { |address| address.country == "China" } - end - end - end - - person.addresses.find_by_country("Mongolia") # returns address - person.addresses.chinese # returns [ address ] - -Custom Association Names ------------------------- - -You can name your associations whatever you like, but if the class cannot be inferred by -Mongoid from the name, and neither can the opposite side you'll want to provide the -macro with some additional options to tell Mongoid how to hook them up. - -.. code-block:: ruby - - class Car - include Mongoid::Document - embeds_one :engine, class_name: "Motor", inverse_of: :machine - end - - class Motor - include Mongoid::Document - embedded_in :machine, class_name: "Car", inverse_of: :engine - end - -Custom Primary & Foreign Keys ------------------------------ - -The fields used when looking up associations can be explicitly specified. -The default is to use ``id`` on the "parent" association and ``#{association_name}_id`` -on the "child" association, for example with a has_many/belongs_to: - -.. code-block:: ruby - - class Company - include Mongoid::Document - has_many :emails - end - - class Email - include Mongoid::Document - belongs_to :company - end - - company = Company.find(id) - # looks up emails where emails.company_id == company.id - company.emails - -Specify a different ``primary_key`` to change the field name on the "parent" -association and ``foreign_key`` to change the field name on the "child" -association: - -.. code-block:: ruby - - class Company - include Mongoid::Document - field :c, type: String - has_many :emails, foreign_key: 'c_ref', primary_key: 'c' - end - - class Email - include Mongoid::Document - # This definition of c_ref is automatically generated by Mongoid: - # field :c_ref, type: Object - # But the type can also be specified: - field :c_ref, type: String - belongs_to :company, foreign_key: 'c_ref', primary_key: 'c' - end - - company = Company.find(id) - # looks up emails where emails.c_ref == company.c - company.emails - -With a has_and_belongs_to_many association, since the data is stored on both -sides of the association, there are 4 fields configurable when the association -is defined: - -- ``:primary_key`` is the field on the remote model that contains the value - by which the remote model is looked up. -- ``:foreign_key`` is the field on the local model which stores the - ``:primary_key`` values. -- ``:inverse_primary_key`` is the field on the local model that the remote - model uses to look up the local model documents. -- ``:inverse_foreign_key`` is the field on the remote model storing the - values in ``:inverse_primary_key``. - -An example might make this more clear: - -.. code-block:: ruby - - class Company - include Mongoid::Document - - field :c_id, type: Integer - field :e_ids, type: Array - - has_and_belongs_to_many :employees, - primary_key: :e_id, foreign_key: :e_ids, - inverse_primary_key: :c_id, inverse_foreign_key: :c_ids - end - - class Employee - include Mongoid::Document - - field :e_id, type: Integer - field :c_ids, type: Array - - has_and_belongs_to_many :companies, - primary_key: :c_id, foreign_key: :c_ids, - inverse_primary_key: :e_id, inverse_foreign_key: :e_ids - end - - company = Company.create!(c_id: 123) - # => # - - employee = Employee.create!(e_id: 456) - # => # - - company.employees << employee - - company - # => # - - employee - # => # - -Note that just like with the default ``#{association_name}_id`` field, -Mongoid automatically adds a field for the custom foreign key ``c_ref`` to -the model. However, since Mongoid doesn't know what type of data should be -allowed in the field, the field is created with a type of Object. It is a -good idea to explicitly define the field with the appropriate type. - - -.. _association-scope: - -Custom Scopes -------------- - -You may set a specific scope on an association using the ``:scope`` parameter. -The scope is an additional filter that restricts which objects are considered -to be a part of the association - a scoped association will return only -documents which satisfy the scope condition.. The scope may be either: - -- a ``Proc`` with arity zero, or -- a ``Symbol`` which references a :ref:`named scope ` on the - associated model. - -.. code-block:: ruby - - class Trainer - has_many :pets, scope: -> { where(species: 'dog') } - has_many :toys, scope: :rubber - end - - class Pet - belongs_to :trainer - end - - class Toy - scope :rubber, where(material: 'rubber') - belongs_to :trainer - end - -.. note:: - - It is possible to add documents that do not satisfy an association's scope - to that association. In this case, such documents will appear associated - in memory, and will be saved to the database, but will not be present when - the association is queried in the future. For example: - - .. code-block:: ruby - - trainer = Trainer.create! - dog = Pet.create!(trainer: trainer, species: 'dog') - cat = Pet.create!(trainer: trainer, species: 'cat') - - trainer.pets #=> [dog, cat] - - trainer.reload.pets #=> [dog] - -.. note:: - - Mongoid's syntax for scoped association differs from that of ActiveRecord. - Mongoid uses the ``:scope`` keyword argument for consistency with other - association options, whereas in ActiveRecord the scope is a positional - argument. - - -Validations ------------ - -It is important to note that by default, Mongoid will validate the children of any -association that are loaded into memory via a ``validates_associated``. The associations that -this applies to are: - -- ``embeds_many`` -- ``embeds_one`` -- ``has_many`` -- ``has_one`` -- ``has_and_belongs_to_many`` - -If you do not want this behavior, you may turn it off when defining the association. - -.. code-block:: ruby - - class Person - include Mongoid::Document - - embeds_many :addresses, validate: false - has_many :posts, validate: false - end - - -Polymorphism ------------- - -One to one and one to many associations support polymorphism, which is -having a single association potentially contain objects of different classes. -For example, we could model an organization in which departments and teams -have managers as follows: - -.. code-block:: ruby - - class Department - include Mongoid::Document - - has_one :manager, as: :unit - end - - class Team - include Mongoid::Document - - has_one :manager, as: :unit - end - - class Manager - include Mongoid::Document - - belongs_to :unit, polymorphic: true - end - - dept = Department.create! - team = Team.create! - - alice = Manager.create!(unit: dept) - alice.unit == dept - # => true - dept.manager == alice - # => true - -To provide another example, suppose we want to track price history for -products and bundles. This can be achieved via an embedded one to many -polymorphic association: - -.. code-block:: ruby - - class Product - include Mongoid::Document - - field :name, type: String - has_and_belongs_to_many :bundles - - embeds_many :prices, as: :item - end - - class Bundle - include Mongoid::Document - - field :name, type: String - has_and_belongs_to_many :products - - embeds_many :prices, as: :item - end - - class Price - include Mongoid::Document - - embedded_in :item, polymorphic: true - end - - pants = Product.create!(name: 'Pants', - prices: [Price.new, Price.new]) - costume = Bundle.create!(name: 'Costume', products: [pants], - prices: [Price.new, Price.new]) - -To define a polymorphic association, specify the ``polymorphic: true`` option -on the child association and add the ``as: :association_name`` option to the -parent association. - -Note that Mongoid currently supports polymorphism only in one direction - from -the child to the parent. For example, polymorphism cannot be used to specify -that a bundle may contain other bundles or products: - -.. code-block:: ruby - - class Bundle - include Mongoid::Document - - # Does not work: - has_many :items, polymorphic: true - end - -``has_and_belongs_to_many`` associations do not support polymorphism. - - -Cascading Callbacks -------------------- - -If you want the embedded document callbacks to fire when calling a persistence -operation on its parent, you will need to provide the cascade callbacks option -to the association. - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_many :albums, cascade_callbacks: true - embeds_one :label, cascade_callbacks: true - end - - band.save # Fires all save callbacks on the band, albums, and label. - - -.. _dependent-behavior: - -Dependent Behavior ------------------- - -You can provide dependent options to referenced associations to instruct Mongoid -how to handle situations where one side of the association is deleted, or is attempted -to be deleted. The options are as follows: - -- ``:delete_all``: Delete the child document(s) without running any of the model callbacks. -- ``:destroy``: Destroy the child document(s) and run all of the model callbacks. -- ``:nullify``: Set the foreign key field of the child document to nil. The child may become orphaned if it is ordinarily only referenced via the parent. -- ``:restrict_with_exception``: ``raise`` an error if the child is not empty. -- ``:restrict_with_error``: Cancel operation and return false if the child is not empty. - -If no ``:dependent`` option is provided, deleting the parent document leaves the child document unmodified -(in other words, the child document continues to reference the now deleted parent document via the foreign key field). -The child may become orphaned if it is ordinarily only referenced via the parent. - -.. code-block:: ruby - - class Band - include Mongoid::Document - has_many :albums, dependent: :delete_all - belongs_to :label, dependent: :nullify - end - - class Album - include Mongoid::Document - belongs_to :band - end - - class Label - include Mongoid::Document - has_many :bands, dependent: :restrict_with_exception - end - - label = Label.first - label.bands.push(Band.first) - label.delete # Raises an error since bands is not empty. - - Band.first.destroy # Will delete all associated albums. - - -Autosaving ----------- - -One core difference between Mongoid and ActiveRecord is that Mongoid does not -automatically save associated documents for referenced (i.e., non-embedded) -associations when the parent is saved, for performance reasons. - -If autosaving is not used, it is possible to create dangling references -to non-existent documents via associations: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_many :albums - end - - class Album - include Mongoid::Document - - belongs_to :band - end - - band = Band.new - album = Album.create!(band: band) - - # The band is not persisted at this point. - - album.reload - - album.band_id - # => BSON::ObjectId('6257699753aefe153121a3d5') - - # Band does not exist. - album.band - # => nil - -To make referenced associations save automatically when the parent is saved, -add the ``:autosave`` option to the association: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - has_many :albums - end - - class Album - include Mongoid::Document - - belongs_to :band, autosave: true - end - - band = Band.new - album = Album.create!(band: band) - - # The band is persisted at this point. - - album.reload - - album.band_id - # => BSON::ObjectId('62576b4b53aefe178b65b8e3') - - album.band - # => # - -The autosaving functionality is automatically added to an association when -using ``accepts_nested_attributes_for``, so that the application does not -need to track which associations were modified when processing a form -submission. - -Embedded associations always autosave, because they are stored as part of the -parent document. - -Some operations on associations always save the parent and the child documents -as part of the operation, regardless of whether autosaving is enabled. -A non-exhaustive list of these operations is as follows: - -- Assignment to the association: - - .. code-block:: ruby - - # Saves the band and the album. - band.albums = [Album.new] - -- ``push``, ``<<``: - - .. code-block:: ruby - - band.albums << Album.new - band.albums.push(Album.new) - - -Existence Predicates --------------------- - -All associations have existence predicates on them in the form of ``name?`` and ``has_name?`` -to check if the association is blank. - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_one :label - embeds_many :albums - end - - band.label? - band.has_label? - band.albums? - band.has_albums? - -Autobuilding ------------- - -One to one associations (``embeds_one``, ``has_one``) have an autobuild option which tells -Mongoid to instantiate a new document when the association is accessed and it is ``nil``. - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_one :label, autobuild: true - has_one :producer, autobuild: true - end - - band = Band.new - band.label # Returns a new empty label. - band.producer # Returns a new empty producer. - -Touching --------- -Any ``belongs_to`` association can take an optional ``:touch`` option which -will cause the parent document to be touched whenever the child document is -updated: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name - belongs_to :label, touch: true - end - - band = Band.first - band.name = "The Rolling Stones" - band.save! # Calls touch on the parent label. - band.touch # Calls touch on the parent label. - -``:touch`` can also take a string or symbol argument specifying a field to -be touched on the parent association in addition to updated_at: - -.. code-block:: ruby - - class Label - include Mongoid::Document - include Mongoid::Timestamps - field :bands_updated_at, type: Time - has_many :bands - end - - class Band - include Mongoid::Document - belongs_to :label, touch: :bands_updated_at - end - - label = Label.create! - band = Band.create!(label: label) - - band.touch # Updates updated_at and bands_updated_at on the label. - -When an embedded document is touched, its parents are recursively touched -through the composition root (because all of the parents are necessarily saved -when the embedded document is saved). The ``:touch`` attribute therefore is -unnecessary on ``embedded_in`` associations. - -Mongoid currently `does not support specifying an additional field to be -touched on an embedded_in association `_. - -``:touch`` should not be set to ``false`` on an ``embedded_in`` association, -since composition hierarchy is always updated upon a touch of an embedded -document. This is currently not enforced but enforcement is `intended in the -future `_. - -The counter_cache Option ------------------------- - -As with ActiveRecord, the ``:counter_cache`` option can be used on an association -to make finding the number of belonging objects more efficient. Also similar -to ActiveRecord, you must take into account that there will be an extra -attribute on the associated model. This means that with Mongoid, -you need to include ``Mongoid::Attributes::Dynamic`` on the associated model. -For example: - -.. code-block:: ruby - - class Order - include Mongoid::Document - belongs_to :customer, counter_cache: true - end - - class Customer - include Mongoid::Document - include Mongoid::Attributes::Dynamic - has_many :orders - end - -Association Proxies -------------------- - -Associations employ transparent proxies to the target objects. This can -cause surprising behavior in some situations. - -The method visibility may be lost when methods on association targets are -accessed, depending on the association: - -.. code-block:: ruby - - class Order - include Mongoid::Document - belongs_to :customer - - private - - def internal_status - 'new' - end - end - - class Customer - include Mongoid::Document - has_many :orders - - private - - def internal_id - 42 - end - end - - order = Order.new - customer = Customer.create!(orders: [order]) - - # has_many does not permit calling private methods on the target - customer.orders.first.internal_status - # NoMethodError (private method `internal_status' called for #) - - # belongs_to permits calling private methods on the target - order.customer.internal_id - # => 42 - -Association Metadata -==================== - -All associations in Mongoid contain metadata that holds information about the association in -question, and is a valuable tool for third party developers to use to extend Mongoid. - -You can access the association metadata of the association in a few different ways. - -.. code-block:: ruby - - # Get the metadata for a named association from the class or document. - Model.reflect_on_association(:association_name) - model.reflect_on_association(:association_name) - - # Get the metadata with a specific association itself on a specific - # document. - model.associations[:association_name] - -Attributes ----------- - -All associations contain a ``_target``, which is the proxied document or documents, a ``_base`` -which is the document the association hangs off, and ``_association`` which provides information -about the association. - -.. code-block:: ruby - - class Person - include Mongoid::Document - embeds_many :addresses - end - - person.addresses = [ address ] - person.addresses._target # returns [ address ] - person.addresses._base # returns person - person.addresses._association # returns the association metadata - -The Association Object ----------------------- - -The association object itself contains more information than one might know what to do -with, and is useful for developers of extensions to Mongoid. - - -.. list-table:: - :header-rows: 1 - :widths: 30 60 - - * - Method - - Description - * - ``Association#as`` - - Returns the name of the parent to a polymorphic child. - * - ``Association#as?`` - - Returns whether or not an as option exists. - * - ``Association#autobuilding?`` - - Returns whether or not the association is autobuilding. - * - ``Association#autosaving?`` - - Returns whether or not the association is autosaving. - * - ``Association#cascading_callbacks?`` - - Returns whether the association has callbacks cascaded down from the parent. - * - ``Association#class_name`` - - Returns the class name of the proxied document. - * - ``Association#cyclic?`` - - Returns whether the association is a cyclic association. - * - ``Association#dependent`` - - Returns the association's dependent option. - * - ``Association#destructive?`` - - Returns true if the association has a dependent delete or destroy. - * - ``Association#embedded?`` - - Returns whether the association is embedded in another document. - * - ``Association#forced_nil_inverse?`` - - Returns whether the association has a nil inverse defined. - * - ``Association#foreign_key`` - - Returns the name of the foreign key field. - * - ``Association#foreign_key_check`` - - Returns the name of the foreign key field dirty check method. - * - ``Association#foreign_key_setter`` - - Returns the name of the foreign key field setter. - * - ``Association#indexed?`` - - Returns whether the foreign key is auto indexed. - * - ``Association#inverses`` - - Returns the names of all inverse association. - * - ``Association#inverse`` - - Returns the name of a single inverse association. - * - ``Association#inverse_class_name`` - - Returns the class name of the association on the inverse side. - * - ``Association#inverse_foreign_key`` - - Returns the name of the foreign key field on the inverse side. - * - ``Association#inverse_klass`` - - Returns the class of the association on the inverse side. - * - ``Association#inverse_association`` - - Returns the metadata of the association on the inverse side. - * - ``Association#inverse_of`` - - Returns the explicitly defined name of the inverse association. - * - ``Association#inverse_setter`` - - Returns the name of the method used to set the inverse. - * - ``Association#inverse_type`` - - Returns the name for the polymorphic type field of the inverse. - * - ``Association#inverse_type_setter`` - - Returns the name for the polymorphic type field setter of the inverse. - * - ``Association#key`` - - Returns the name of the field in the attributes hash to use to get the association. - * - ``Association#klass`` - - Returns the class of the proxied documents in the association. - * - ``Association#name`` - - Returns the association name. - * - ``Association#options`` - - Returns self, for API compatibility with ActiveRecord. - * - ``Association#order`` - - Returns the custom sorting options on the association. - * - ``Association#polymorphic?`` - - Returns whether the association is polymorphic. - * - ``Association#setter`` - - Returns the name of the field to set the association. - * - ``Association#store_as`` - - Returns the name of the attribute to store an embedded association in. - * - ``Association#touchable?`` - - Returns whether or not the association has a touch option. - * - ``Association#type`` - - Returns the name of the field to get the polymorphic type. - * - ``Association#type_setter`` - - Returns the name of the field to set the polymorphic type. - * - ``Association#validate?`` - - Returns whether the association has an associated validation. diff --git a/source/reference/callbacks.txt b/source/reference/callbacks.txt deleted file mode 100644 index 972451dd..00000000 --- a/source/reference/callbacks.txt +++ /dev/null @@ -1,120 +0,0 @@ -.. _callbacks: - -********* -Callbacks -********* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Mongoid implements many of the `ActiveRecord callbacks -`_. - - -Document Callbacks -================== - -Mongoid supports the following callbacks for :doc:`documents `: - -- ``after_initialize`` -- ``after_build`` -- ``before_validation`` -- ``after_validation`` -- ``before_create`` -- ``around_create`` -- ``after_create`` -- ``after_find`` -- ``before_update`` -- ``around_update`` -- ``after_update`` -- ``before_upsert`` -- ``around_upsert`` -- ``after_upsert`` -- ``before_save`` -- ``around_save`` -- ``after_save`` -- ``before_destroy`` -- ``around_destroy`` -- ``after_destroy`` - -Callbacks are available on any document, whether it is embedded within -another document or not. Note that to be efficient, Mongoid only invokes -the callback on the document that the persistence action was executed on. -This enables Mongoid to support large hierarchies and to handle optimized -atomic updates efficiently (without invoking callbacks throughout the document -hierarchy). - -Note that using callbacks for domain logic is a bad design practice, and can -lead to unexpected errors that are hard to debug when callbacks in -the chain halt execution. It is our recommendation to only use them -for cross-cutting concerns, like queueing up background jobs. - -.. code-block:: ruby - - class Article - include Mongoid::Document - field :name, type: String - field :body, type: String - field :slug, type: String - - before_create :send_message - - after_save do |document| - # Handle callback here. - end - - protected - def send_message - # Message sending code here. - end - end - -Callbacks are coming from Active Support, so you can use the new -syntax as well: - -.. code-block:: ruby - - class Article - include Mongoid::Document - field :name, type: String - - set_callback(:create, :before) do |document| - # Message sending code here. - end - end - - -Association Callbacks -===================== - -Mongoid has a set of callbacks that are specific to associations - these are: - -- ``after_add`` -- ``after_remove`` -- ``before_add`` -- ``before_remove`` - -Each time a document is added or removed from any of the following -associations, the respective callbacks are invoked: ``embeds_many``, -``has_many`` and ``has_and_belongs_to_many``. - -Association callbacks are specified as options on the respective association. -The document added/removed will be passed as the parameter to the specified -callback. Example: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - has_many :posts, after_add: :send_email_to_subscribers - end - - def send_email_to_subscribers(post) - Notifications.new_post(post).deliver - end diff --git a/source/reference/configuration.txt b/source/reference/configuration.txt deleted file mode 100644 index c948673d..00000000 --- a/source/reference/configuration.txt +++ /dev/null @@ -1,1027 +0,0 @@ -.. _configuration: - -************* -Configuration -************* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Mongoid is customarily configured through a ``mongoid.yml`` file that specifies -options and clients. The simplest configuration is as follows, which configures -Mongoid to talk to a MongoDB server at "localhost:27017" and use the database -named "mongoid". - -.. code-block:: yaml - - development: - clients: - default: - database: mongoid - hosts: - - localhost:27017 - -The top level key in the configuration file, ``development`` in the above -example, refers to the environment name which the application is executing in, -i.e. ``development``, ``test`` or ``production``. The third level key, -``default`` in the above example, refers to the Mongo client name. -Most applications will use a single client named ``default``. - - -Generating Default Configuration -================================ - -If you are using {+ror+}, you can have Mongoid generate a default -configuration file for you by running the following command: - -.. code-block:: bash - - rails g mongoid:config - -The configuration file will be placed in ``config/mongoid.yml``. An -initializer will also be created and placed in -``config/initializers/mongoid.rb``. It is recommended that all configuration -be specified in ``config/mongoid.yml``, but if you prefer, the ``mongoid.rb`` -initializer may also be used to set configuration options. Note, though, that -settings in ``mongoid.yml`` always take precedence over settings in the -initializer. - -If you are not using {+ror+}, you can copy the minimal configuration -given above and save it as ``config/mongoid.yml``. - - -Loading Mongoid Configuration -============================= - -If you are using {+ror+}, Mongoid configuration is automatically loaded -for the current environment as stored in ``Rails.env`` when the application -loads. - -You may need to configure the ORM for your application to be Mongoid by -adding the following to ``application.rb``: - -.. code-block:: ruby - - config.generators do |g| - g.orm :mongoid - end - -If you are not using {+ror+}, Mongoid configuration must be loaded -manually. This can be done via the ``Mongoid.load!`` method, which takes -the configuration file path as its argument, as follows: - -.. code-block:: ruby - - # Use automatically detected environment name - Mongoid.load!("path/to/your/mongoid.yml") - - # Specify environment name manually - Mongoid.load!("path/to/your/mongoid.yml", :production) - -When Mongoid is asked to automatically detect the environment name, -it does so by examining the following sources, in order: - -- If ``Rails`` top level constant is defined, ``Rails.env``. -- If ``Sinatra`` top level constant is defined, ``Sinatra::Base.environment``. -- The ``RACK_ENV`` environment variable. -- The ``MONGOID_ENV`` environment variable. - -It is also possible to configure Mongoid directly in Ruby, without using -a configuration file. This configuration style does not support the concept -of environments - whatever configuration is provided, it is applied to the -current environment - but it does support defining multiple clients. - -.. code-block:: ruby - - Mongoid.configure do |config| - config.clients.default = { - hosts: ['localhost:27017'], - database: 'my_db', - } - - config.log_level = :warn - end - -.. note:: - - Mongoid must be configured *before* any component of it is used or referenced. - Once a component is used or referenced, changing configuration may not apply - changes to already instantiated components. - - -.. _configuration-options: - -Mongoid Configuration Options -============================= - -The following annotated example ``mongoid.yml`` demonstrates how Mongoid -can be configured. - -Mongoid delegates to the Ruby driver for client configuration. Please review -`the driver documentation `_ -for details on driver options. - -.. code-block:: yaml - - development: - # Configure available database clients. (required) - clients: - # Defines the default client. (required) - default: - # Mongoid can connect to a URI accepted by the driver: - # uri: mongodb://user:password@mongodb.domain.com:27017/my_db_development - - # Otherwise define the parameters separately. - # This defines the name of the default database that Mongoid can connect to. - # (required). - database: my_db_development - # Provides the hosts the default client can connect to. Must be an array - # of host:port pairs. (required) - hosts: - - localhost:27017 - options: - # Note that all options listed below are Ruby driver client options (the mongo gem). - # Please refer to the driver documentation of the version of the mongo gem you are using - # for the most up-to-date list of options. - - # Change the default write concern. (default = { w: 1 }) - # write: - # w: 1 - - # Change the default read preference. Valid options for mode are: :secondary, - # :secondary_preferred, :primary, :primary_preferred, :nearest - # (default: primary) - # read: - # mode: :secondary_preferred - # tag_sets: - # - use: web - - # The name of the user for authentication. - # user: 'user' - - # The password of the user for authentication. - # password: 'password' - - # The user's database roles. - # roles: - # - 'dbOwner' - - # Change the default authentication mechanism. Valid options include: - # :scram, :scram256, :mongodb_cr, :mongodb_x509, :gssapi, :aws, :plain. - # MongoDB Server defaults to :scram, which will use "SCRAM-SHA-256" if available, - # otherwise fallback to "SCRAM-SHA-1" (:scram256 will always use "SCRAM-SHA-256".) - # This setting is handled by the MongoDB Ruby Driver. Please refer to: - # https://mongodb.com/docs/ruby-driver/current/reference/authentication/ - # auth_mech: :scram - - # The database or source to authenticate the user against. - # (default: the database specified above or admin) - # auth_source: admin - - # Force a the driver cluster to behave in a certain manner instead of auto- - # discovering. Can be one of: :direct, :replica_set, :sharded. Set to :direct - # when connecting to hidden members of a replica set. - # connect: :direct - - # Changes the default time in seconds the server monitors refresh their status - # via hello commands. (default: 10) - # heartbeat_frequency: 10 - - # The time in seconds for selecting servers for a near read preference. (default: 0.015) - # local_threshold: 0.015 - - # The timeout in seconds for selecting a server for an operation. (default: 30) - # server_selection_timeout: 30 - - # The maximum number of connections in the connection pool. (default: 5) - # max_pool_size: 5 - - # The minimum number of connections in the connection pool. (default: 1) - # min_pool_size: 1 - - # The time to wait, in seconds, in the connection pool for a connection - # to be checked in before timing out. (default: 5) - # wait_queue_timeout: 5 - - # The time to wait to establish a connection before timing out, in seconds. - # (default: 10) - # connect_timeout: 10 - - # How long to wait for a response for each operation sent to the - # server. This timeout should be set to a value larger than the - # processing time for the longest operation that will be executed - # by the application. Note that this is a client-side timeout; - # the server may continue executing an operation after the client - # aborts it with the SocketTimeout exception. - # (default: nil, meaning no timeout) - # socket_timeout: 5 - - # The name of the replica set to connect to. Servers provided as seeds that do - # not belong to this replica set will be ignored. - # replica_set: name - - # Compressors to use for wire protocol compression. (default is to not use compression) - # "zstd" requires zstd-ruby gem. "snappy" requires snappy gem. - # Refer to: https://www.mongodb.com/docs/ruby-driver/current/reference/create-client/#compression - # compressors: ["zstd", "snappy", "zlib"] - - # Whether to connect to the servers via ssl. (default: false) - # ssl: true - - # The certificate file used to identify the connection against MongoDB. - # ssl_cert: /path/to/my.cert - - # The private keyfile used to identify the connection against MongoDB. - # Note that even if the key is stored in the same file as the certificate, - # both need to be explicitly specified. - # ssl_key: /path/to/my.key - - # A passphrase for the private key. - # ssl_key_pass_phrase: password - - # Whether to do peer certification validation. (default: true) - # ssl_verify: true - - # The file containing concatenated certificate authority certificates - # used to validate certs passed from the other end of the connection. - # ssl_ca_cert: /path/to/ca.cert - - # Whether to truncate long log lines. (default: true) - # truncate_logs: true - - # Configure Mongoid-specific options. (optional) - options: - # Allow BSON::Decimal128 to be parsed and returned directly in - # field values. When BSON 5 is present and the this option is set to false - # (the default), BSON::Decimal128 values in the database will be returned - # as BigDecimal. - # - # @note this option only has effect when BSON 5+ is present. Otherwise, - # the setting is ignored. - # allow_bson5_decimal128: false - - # Application name that is printed to the MongoDB logs upon establishing - # a connection. Note that the name cannot exceed 128 bytes in length. - # It is also used as the database name if the database name is not - # explicitly defined. (default: nil) - # app_name: nil - - # When this flag is false, callbacks for embedded documents will not be - # called. This is the default in 9.0. - # - # Setting this flag to true restores the pre-9.0 behavior, where callbacks - # for embedded documents are called. This may lead to stack overflow errors - # if there are more than cicrca 1000 embedded documents in the root - # document's dependencies graph. - # See https://jira.mongodb.org/browse/MONGOID-5658 for more details. - # around_callbacks_for_embeds: false - - # Sets the async_query_executor for the application. By default the thread pool executor - # is set to `:immediate`. Options are: - # - # - :immediate - Initializes a single +Concurrent::ImmediateExecutor+ - # - :global_thread_pool - Initializes a single +Concurrent::ThreadPoolExecutor+ - # that uses the +async_query_concurrency+ for the +max_threads+ value. - # async_query_executor: :immediate - - # Mark belongs_to associations as required by default, so that saving a - # model with a missing belongs_to association will trigger a validation - # error. - # belongs_to_required_by_default: true - - # Set the global discriminator key. - # discriminator_key: "_type" - - # Raise an exception when a field is redefined. - # duplicate_fields_exception: false - - # Defines how many asynchronous queries can be executed concurrently. - # This option should be set only if `async_query_executor` is set - # to `:global_thread_pool`. - # global_executor_concurrency: nil - - # When this flag is true, any attempt to change the _id of a persisted - # document will raise an exception (`Errors::ImmutableAttribute`). - # This is the default in 9.0. Setting this flag to false restores the - # pre-9.0 behavior, where changing the _id of a persisted - # document might be ignored, or it might work, depending on the situation. - # immutable_ids: true - - # Include the root model name in json serialization. - # include_root_in_json: false - - # # Include the _type field in serialization. - # include_type_for_serialization: false - - # Whether to join nested persistence contexts for atomic operations - # to parent contexts by default. - # join_contexts: false - - # When this flag is false (the default as of Mongoid 9.0), a document that - # is created or loaded will remember the storage options that were active - # when it was loaded, and will use those same options by default when - # saving or reloading itself. - # - # When this flag is true you'll get pre-9.0 behavior, where a document will - # not remember the storage options from when it was loaded/created, and - # subsequent updates will need to explicitly set up those options each time. - # - # For example: - # - # record = Model.with(collection: 'other_collection') { Model.first } - # - # This will try to load the first document from 'other_collection' and - # instantiate it as a Model instance. Pre-9.0, the record object would - # not remember that it came from 'other_collection', and attempts to - # update it or reload it would fail unless you first remembered to - # explicitly specify the collection every time. - # - # As of Mongoid 9.0, the record will remember that it came from - # 'other_collection', and updates and reloads will automatically default - # to that collection, for that record object. - # legacy_persistence_context_behavior: false - - # When this flag is false, a document will become read-only only once the - # #readonly! method is called, and an error will be raised on attempting - # to save or update such documents, instead of just on delete. When this - # flag is true, a document is only read-only if it has been projected - # using #only or #without, and read-only documents will not be - # deletable/destroyable, but they will be savable/updatable. - # When this feature flag is turned on, the read-only state will be reset on - # reload, but when it is turned off, it won't be. - # legacy_readonly: false - - # The log level. - # - # It must be set prior to referencing clients or Mongo.logger, - # changes to this option are not be propagated to any clients and - # loggers that already exist. - # - # Additionally, only when the clients are configured via the - # configuration file is the log level given by this option honored. - # log_level: :info - - # Store BigDecimals as Decimal128s instead of strings in the db. - # map_big_decimal_to_decimal128: true - - # Preload all models in development, needed when models use inheritance. - # preload_models: false - - # When this flag is true, callbacks for every embedded document will be - # called only once, even if the embedded document is embedded in multiple - # documents in the root document's dependencies graph. - # This is the default in 9.0. Setting this flag to false restores the - # pre-9.0 behavior, where callbacks are called for every occurrence of an - # embedded document. The pre-9.0 behavior leads to a problem that for multi - # level nested documents callbacks are called multiple times. - # See https://jira.mongodb.org/browse/MONGOID-5542 - # prevent_multiple_calls_of_embedded_callbacks: true - - # Raise an error when performing a #find and the document is not found. - # raise_not_found_error: true - - # Raise an error when defining a scope with the same name as an - # existing method. - # scope_overwrite_exception: false - - # Return stored times as UTC. - # use_utc: false - - # Configure Driver-specific options. (optional) - driver_options: - # When this flag is off, an aggregation done on a view will be executed over - # the documents included in that view, instead of all documents in the - # collection. When this flag is on, the view filter is ignored. - # broken_view_aggregate: true - - # When this flag is set to false, the view options will be correctly - # propagated to readable methods. - # broken_view_options: true - - # When this flag is set to true, the update and replace methods will - # validate the parameters and raise an error if they are invalid. - # validate_update_replace: false - - -.. _load-defaults: - -Version Based Defaults -====================== - -Mongoid supports setting the configuration options to the defaults for specific -versions. This is useful for upgrading to a new Mongoid version. When upgrading -your Mongoid version, the following should be set on ``Mongoid::Config``: - -.. code:: ruby - - Mongoid.configure do |config| - config.load_defaults - end - -This way, when upgrading to a new version of Mongoid, your code will run with -the configuration options from the previous version of Mongoid. Then, -one-by-one, you can change the feature flags for the new version, and test that -your code still acts as expected. Once all of the new feature flags have been -accounted for, the call to ``load_defaults`` may be changed to take in the *new* -version, and all of the changed feature flags may be removed. - -For example, suppose we're upgrading from 7.5 to 8.0. Between these two versions, -two feature flags were added: ``legacy_attributes`` and ``map_big_decimal_to_decimal128``. -Before upgrading to Mongoid 8, add the following to your ``Mongoid::Config``: - -.. code:: ruby - - Mongoid.configure do |config| - config.load_defaults 7.5 - end - -After upgrading to Mongoid 8.0 in your ``Gemfile``, any feature flags will -remain set to their 7.5 default behavior: ``legacy_attributes: true, -map_big_decimal_to_decimal128: false``. You may then flip these feature flags -one-by-one to their 8.0 behavior: - -.. code:: ruby - - Mongoid.configure do |config| - config.load_defaults 7.5 - config.legacy_attributes = false - # config.map_big_decimal_to_decimal128 = true - end - -We recommend do these one at a time, so in the example above we leave the -second flag commented out. After verifying your code works as expected with the -``legacy_attributes`` flag turned off, the ``map_big_decimal_to_decimal128`` -setting can be uncommented. Once that functionality is verified as well, both -of those lines can be removed and the ``load_defaults`` replaced with: - -.. code:: ruby - - Mongoid.configure do |config| - config.load_defaults 8.0 - end - - -ERb Preprocessing -================= - -When loading a configuration file, Mongoid processes it with ERb before -parsing it as YAML. This allows, for example, constructing the contents of -the configuration file at runtime based on environment variables: - -.. code-block:: yaml - - development: - clients: - default: - uri: "<%= ENV['MONGODB_URI'] %>" - -.. note:: - - When outputting values from ERb, ensure the values are valid YAML and - escape them as needed. - -.. note:: - - Since ERb rendering is performed prior to YAML parsing, all ERb directives - in the configuration file are evaluated, including those occurring in YAML - comments. - -Logging -======= - -When configuring logging, it is important to keep in mind that Mongoid -provides a model layer on top of the MongoDB Ruby driver, and the driver -dispatches the CRUD operations to the MongoDB deployment. Therefore, some -of the logging output in an application using Mongoid comes from Mongoid -itself, and some comes from the driver. - -The Mongo client is a Ruby driver client instance, therefore -the logger of a Mongo client is the Ruby driver logger, not the Mongoid -logger. In other words: - -.. code-block:: ruby - - # Ruby driver logger, not Mongoid logger - Mongoid.client(:default).logger - -Depending on whether Mongoid is used in a {+ror+} application, and how -both Mongoid and Ruby driver are configured, they may use the same logger -instance or different instances, potentially with different configurations. - -In {+ror+} Application ----------------------------- - -When used in a {+ror+} application, Mongoid by default inherits -the logger and the log level from Rails, and sets the driver's logger -to the same logger instance: - -.. code-block:: ruby - - Rails.logger === Mongoid.logger - # => true - - Mongoid.logger === Mongo::Logger.logger - # => true - -To change the log level, use `standard Rails configuration -`_. -Place the following in one of environment configuration files, such as -``config/environments/production.rb``: - -.. code-block:: ruby - - Rails.application.configure do - config.log_level = :debug - end - -.. 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. - -To configure either Mongoid or driver logger differently from the Rails logger, -use an initializer as follows: - -.. code-block:: ruby - - Rails.application.configure do - config.after_initialize do - # Change Mongoid log destination and/or level - Mongoid.logger = Logger.new(STDERR).tap do |logger| - logger.level = Logger::DEBUG - end - - # Change driver log destination and/or level - Mongo::Logger.logger = Logger.new(STDERR).tap do |logger| - logger.level = Logger::DEBUG - end - end - end - -.. note:: - - There is currently no provision in the Ruby standard library ``Logger`` - to return the log device (i.e. the ``IO`` object) that a logger is using. - To have, for example, Mongoid and/or the Ruby driver log to the - standard Rails log file (e.g. ``log/development.log``) but with a - different level from standard Rails logger (``Rails.logger``), the - file must be opened separately and the resulting ``IO`` object passed to - the ``Logger`` constructor. - -.. note:: - - Since by default Mongoid sets its own logger and the driver's logger to the - same instance as the Rails logger, modifying any of the instances affects - all of them. For example the following changes log level for all three - loggers, unless the application assigned a separate ``Logger`` instance - to ``Mongo::Logger.logger`` as described above: - - .. code-block:: ruby - - Mongoid::Logger.logger.level = Logger::DEBUG - -Standalone ----------- - -When not loaded in a {+ror+} application, Mongoid respects the -``log_level`` top level configuration option. It can be given in the -configuration file as follows: - -.. code-block:: yaml - - development: - clients: - default: - # ... - options: - log_level: :debug - -... or when configuring Mongoid inline: - -.. code-block:: ruby - - Mongoid.configure do |config| - config.log_level = :debug - end - -The default log destination in Mongoid 7.1 and higher is standard error. -The default log destination in Mongoid 7.0 and lower is standard output. -To change the log destination, create a new logger instance as follows: - -.. code-block:: ruby - - Mongoid.logger = Logger.new(STDERR).tap do |logger| - logger.level = Logger::DEBUG - end - -To change the Ruby driver log level or destination: - -.. code-block:: ruby - - Mongo::Logger.logger = Logger.new(STDERR).tap do |logger| - logger.level = Logger::DEBUG - end - -To set the driver logger to be the same as the Mongoid logger: - -.. code-block:: ruby - - Mongo::Logger.logger = Mongoid.logger - -.. note:: - - Mongoid does not alter the driver's logger when running in - standalone mode. - -.. _time-zones: - -Time Zones -========== - -Mongoid uses ActiveSupport's time zone functionality, which is far -more robust than Ruby's standard library. Importantly, ActiveSupport -allows configuration of ``Time.zone``, a thread-global variable which -provides context for working with date and time values. - -While a thorough treatment of time zones in Ruby is outside the scope -of this tutorial, the easiest and most reliable way of achieving correct -time zone handling is as follows: - -1. Set the operating system's time zone to UTC. For example, on Linux: - -.. code-block:: bash - - cp /usr/share/zoneinfo/UTC /etc/localtime - -2. Set your application default time zone to UTC: - -.. code-block:: ruby - - # If using Rails, in application.rb: - class Application < Rails::Application - config.time_zone = 'UTC' - end - - # If not using Rails: - Time.zone = 'UTC' - -3. In each controller and job class, set the appropriate time zone in a - ``before_filter`` at the earliest possible stage. As an example, - if each user of your application can set their own time zone, - you may wish to do: - -.. code-block:: ruby - - class ApplicationController < ActionController::Base - before_filter :fetch_user, - :set_time_zone - - def set_time_zone - Time.zone = @user.time_zone - end - end - -4. From here, you may work with times naturally in the local time zone. - For example, in a view: - -.. code-block:: ruby - - Turned into a pumpkin after <%= cinderella.updated_at.seconds_after_midnight %> seconds! - -5. Use ActiveSupport methods instead of the Ruby standard library. - - - ``Time.zone.now`` or ``Time.current` instead of ``Time.now`` - - ``Date.current`` instead of ``Date.today`` - - Critically, note that the latter Ruby standard library methods reference - your system time zone (e.g. UTC) and not the value of ``Time.zone``. - As it is very easy to mistake these similarly named methods, we recommend to - use `Rubocop's Rails/TimeZone cop - `_ in your CI. - -Setting time zone on data loaded from MongoDB ---------------------------------------------- - -MongoDB stores all times in UTC without time zone information. -Mongoid models load and returns time values as instances of -``ActiveSupport::TimeWithZone``. You may set the ``use_utc`` option -to control how Mongoid sets the time zone when loading from the database: - -- If false (default), Mongoid will use ``Time.zone`` to set the time - zone of time values are loaded from database. -- If true, Mongoid will always set the time zone as UTC on loaded - time values. - -``use_utc`` only affects how data is loaded, and does not affect -how data is persisted. For example, if you assign a ``Time`` or -``ActiveSupport::TimeWithZone`` instance to a time field, the time -zone information of the assigned instance always will be used -irrespective of the ``use_utc`` setting. Alternatively, if you -assign a string value to a time field, any time zone information -in the string will be used if present. If the string does not include -time zone information it will be parsed according to ``Time.zone``. -To illustrate: - -.. code-block:: ruby - - Time.use_zone("Asia/Kolkata") do - - # String does not include time zone, so "Asia/Kolkata" will be used - ghandi.born_at = "1869-10-02 7:10 PM" - - # Time zone in string (-0600) will be used - amelia.born_at = "1897-07-24 11:30 -0600" - end - - -Configuring ``SSLContext`` -========================== -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 -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`` -array. This can be done in an initializer. The example below adds a hook -that only enables the "AES256-SHA" cipher. - -.. code-block:: ruby - - Mongo.tls_context_hooks.push( - Proc.new { |context| - context.ciphers = ["AES256-SHA"] - } - ) - - # Only the AES256-SHA cipher will be enabled from this point forward - -Every ``Proc`` in ``Mongo.tls_context_hooks`` will be passed an -``OpenSSL::SSL::SSLContext`` object as its sole argument. These procs will -be executed sequentially during socket creation. - -.. warning:: - - TLS context hooks are global and will affect all ``Mongo::Client`` instances - in an application. - -For more information about TLS context hooks, including best practices for -assigning and removing them, see `the Ruby driver documentation `_. - - -Network Compression -=================== - -Mongoid supports compression of messages to and from MongoDB servers. This functionality is provided by -the Ruby driver, which implements the three algorithms that are supported by MongoDB servers: - -- `Snappy `_: ``snappy`` compression - can be used when connecting to MongoDB servers starting with the 3.4 release, - and requires the `snappy `_ library to be - installed. -- `Zlib `_: ``zlib`` compression can be used when - connecting to MongoDB servers starting with the 3.6 release. -- `Zstandard `_: ``zstd`` compression can be - used when connecting to MongoDB servers starting with the 4.2 release, and - requires the `zstd-ruby `_ library to - be installed. - -To use wire protocol compression, configure the Ruby driver options within ``mongoid.yml``: - -.. code-block:: yaml - - development: - # Configure available database clients. (required) - clients: - # Define the default client. (required) - default: - # ... - options: - # These options are Ruby driver options, documented in - # https://mongodb.com/docs/ruby-driver/current/reference/create-client/ - # ... - # Compressors to use. (default is to not use compression) - # Valid values are zstd, zlib or snappy - or any combination of the three - compressors: ["zstd", "snappy"] - -If no compressors are explicitly requested, the driver will not use compression, -even if the required dependencies for one or more compressors are present on the -system. - -The driver chooses the first compressor of the ones requested that is also supported -by the server. The ``zstd`` compressor is recommended as it produces the highest -compression at the same CPU consumption compared to the other compressors. - -For maximum server compatibility all three compressors can be specified, e.g. -as ``compressors: ["zstd", "snappy", "zlib"]``. - - -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 - - development: - clients: - default: - database: blog_development - hosts: [localhost:27017] - options: - auto_encryption_options: - key_vault_namespace: 'keyvault.datakeys' - kms_providers: - local: - key: "z7iYiYKLuYymEWtk4kfny1ESBwwFdA58qMqff96A8ghiOcIK75lJGPUIocku8LOFjQuEgeIP4xlln3s7r93FV9J5sAE7zg8U" - schema_map: - blog_development.comments: - properties: - message: - encrypt: - keyId: - - !ruby/object:BSON::Binary - data: !binary |- - R/AgNcxASFiiJWKXqWGo5w== - type: :uuid - bsonType: "string" - algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" - bsonType: "object" - - -Usage with Forking Servers -========================== - -When using Mongoid with a forking web server such as Puma, or any application -that otherwise forks to spawn child processes, special considerations apply. - -If possible, we recommend to not perform any MongoDB operations in the parent -process prior to forking, which will avoid any forking-related pitfalls. - -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: - -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. - -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`` and ``on_refork`` hooks to close clients in the -parent process (`Puma documentation `_). - -.. code-block:: ruby - - # config/puma.rb - - # Runs in the Puma master process before it forks a child worker. - before_fork do - 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 -------- - -Use the ``after_fork`` hook to reconnect clients in the workers and -the ``before_fork`` hook to close clients in the parent process -(`Unicorn documentation `_): - -.. code-block:: ruby - - # config/unicorn.rb - - before_fork do |_server, _worker| - Mongoid.disconnect_clients - end - - after_fork do |_server, _worker| - Mongoid.reconnect_clients - end - -Passenger ---------- - -Use the ``starting_worker_process`` hook to reconnect clients in the workers -(`Passenger documentation -`_). -Passenger does not appear to have a hook that is invoked in the parent process -before the workers are forked. - -.. code-block:: ruby - - if defined?(PhusionPassenger) - PhusionPassenger.on_event(:starting_worker_process) do |forked| - Mongoid.reconnect_clients if forked - end - end - - -.. _query-cache-middleware: - -Query Cache Middleware -====================== - -Enabling Query Cache for Rack Web Requests ------------------------------------------- - -The MongoDB Ruby Driver provides a Rack middleware which enables the :ref:`Query Cache -` for the duration of each web request. Below is an example of -how to enable the Query Cache Middleware in a {+ror+} application: - -.. code-block:: ruby - - # config/application.rb - - # Add Mongo::QueryCache::Middleware at the bottom of the middleware stack - # or before other middleware that queries MongoDB. - config.middleware.use Mongo::QueryCache::Middleware - -Please refer to the `Rails on Rack guide -`_ -for more information about using Rack middleware in Rails applications. - -Enabling Query Cache for ActiveJob ----------------------------------- - -The MongoDB Ruby Driver also provides Query Cache middleware for ActiveJob. -You may enable it for all jobs in an initializer: - -.. code-block:: ruby - - # config/initializers/active_job.rb - - # Enable Mongo driver query cache for ActiveJob - ActiveSupport.on_load(:active_job) do - include Mongo::QueryCache::Middleware::ActiveJob - end - -Or for a specific job class: - -.. code-block:: ruby - - class MyJob < ActiveJob::Base - include Mongo::QueryCache::Middleware::ActiveJob - end - - -Development Configuration -========================= - -Driver's default configuration is suitable for production deployment. -In development, some settings can be adjusted to provide a better developer -experience. - -- ``:server_selection_timeout``: set this to a low value (e.g., ``1``) - if your MongoDB server is running locally and you start it manually. A low - server selection timeout will cause the driver to fail quickly when there is - no server running. - -Sample recommended development configuration: - -.. code-block:: yaml - - development: - clients: - default: - database: mongoid - hosts: - - localhost:27017 - options: - server_selection_timeout: 1 diff --git a/source/reference/crud.txt b/source/reference/crud.txt deleted file mode 100644 index 33d2b077..00000000 --- a/source/reference/crud.txt +++ /dev/null @@ -1,1074 +0,0 @@ -.. _crud: - -*************** -CRUD Operations -*************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -Saving Documents -================ - -Mongoid supports all expected CRUD operations for those familiar with other -Ruby mappers like Active Record or Data Mapper. What distinguishes Mongoid -from other mappers for MongoDB is that the general persistence operations -perform atomic updates on only the fields that have changed instead of -writing the entire document to the database each time. - -The persistence sections will provide examples on what database operation is -performed when executing the documented command. - -Standard --------- - -Mongoid's standard persistence methods come in the form of common methods you -would find in other mapping frameworks. The following table shows all standard -operations with examples. - -.. list-table:: - :header-rows: 1 - :widths: 30 60 - - * - Operation - - Example - - * - ``Model#attributes`` - - *Returns the document's attributes as a ``Hash`` with string keys, and - its values in Mongoized form (i.e. the way they are stored in the db).* - - *The attributes hash also contains the attributes of all embedded - documents, as well as their embedded documents, etc. If an embedded - association is empty, its key will not show up in the returned hash.* - - - - .. code-block:: ruby - - person = Person.new(first_name: "Heinrich", last_name: "Heine") - - person.attributes - # => { "_id" => BSON::ObjectId('633467d03282a43784c2d56e'), "first_name" => "Heinrich", "last_name" => "Heine" } - - * - ``Model.create!`` - - *Insert a document or multiple documents into the database, raising an - error if a validation or server error occurs.* - - *Pass a hash of attributes to create one document with the specified - attributes, or an array of hashes to create multiple documents. - If a single hash is passed, the corresponding document is returned. - If an array of hashes is passed, an array of documents corresponding - to the hashes is returned.* - - *If a block is given to* ``create!`` *, it will be invoked with each - document as the argument in turn prior to attempting to save that - document.* - - *If there is a problem saving any of the documents, such as - a validation error or a server error, an exception is raised - and, consequently, none of the documents are returned. - However, if an array of hashes was passed and previous documents were - successfully saved, those documents will remain in the database.* - - - .. code-block:: ruby - - Person.create!( - first_name: "Heinrich", - last_name: "Heine" - ) # => Person instance - - Person.create!([ - { first_name: "Heinrich", last_name: "Heine" }, - { first_name: "Willy", last_name: "Brandt" } - ]) # => Array of two Person instances - - Person.create!(first_name: "Heinrich") do |doc| - doc.last_name = "Heine" - end # => Person instance - - * - ``Model.create`` - - *Instantiate a document or multiple documents and, if validations pass, - insert them into the database.* - - ``create`` *is similar to* ``create!`` *but does not raise - exceptions on validation errors. It still raises errors on server - errors, such as trying to insert a document with an* ``_id`` *that - already exists in the collection.* - - *If any validation errors are encountered, the respective document - is not inserted but is returned along with documents that were inserted. - Use* ``persisted?`` *,* ``new_record?`` *or* ``errors`` *methods - to check which of the returned documents were inserted into the - database.* - - - .. code-block:: ruby - - Person.create( - first_name: "Heinrich", - last_name: "Heine" - ) # => Person instance - - Person.create([ - { first_name: "Heinrich", last_name: "Heine" }, - { first_name: "Willy", last_name: "Brandt" } - ]) # => Array of two Person instances - - Person.create(first_name: "Heinrich") do |doc| - doc.last_name = "Heine" - end # => Person instance - - class Post - include Mongoid::Document - - validates_uniqueness_of :title - end - - posts = Post.create([{title: "test"}, {title: "test"}]) - # => array of two Post instances - posts.map { |post| post.persisted? } # => [true, false] - - * - ``Model#save!`` - - *Save the changed attributes to the database atomically, or insert the document if - new. Raises an exception if validations fail or there is a server error.* - - *Returns true if the changed attributes were saved, raises an exception otherwise.* - - - .. code-block:: ruby - - person = Person.new( - first_name: "Heinrich", - last_name: "Heine" - ) - person.save! - - person.first_name = "Christian Johan" - person.save! - - * - ``Model#save`` - - *Save the changed attributes to the database atomically, or insert the document - if new.* - - *Returns true if the changed attributes were saved. Returns false - if there were any validation errors. Raises an exception if - the document passed validation but there was a server error during - the save.* - - *Pass* ``validate: false`` *option to bypass validations.* - - *Pass* ``touch: false`` *option to ignore the updates to the updated_at - field. If the document being save has not been previously persisted, - this option is ignored and the created_at and updated_at fields will be - updated with the current time.* - - - .. code-block:: ruby - - person = Person.new( - first_name: "Heinrich", - last_name: "Heine" - ) - person.save - person.save(validate: false) - person.save(touch: false) - - person.first_name = "Christian Johan" - person.save - - * - ``Model#update_attributes`` - - *Update the document attributes in the database. Will return true if validation passed, - false if not.* - - - .. code-block:: ruby - - person.update_attributes( - first_name: "Jean", - last_name: "Zorg" - ) - - * - ``Model#update_attributes!`` - - *Update the document attributes in the database and raise an error if validation failed.* - - - .. code-block:: ruby - - person.update_attributes!( - first_name: "Leo", - last_name: "Tolstoy" - ) - - * - ``Model#update_attribute`` - - *Update a single attribute, bypassing validations.* - - - .. code-block:: ruby - - person.update_attribute(:first_name, "Jean") - - * - ``Model#upsert`` - - *Performs a MongoDB replace with upsert on the document. If the document - exists in the database and the* ``:replace`` *option is set to true, it - will get overwritten with the current document in the application (any - attributes present in the database but not in the application's document - instance will be lost). If the* ``:replace`` *option is false (default), - the document will be updated, and any attributes not in the application's - document will be maintained. - If the document does not exist in the database, it will be inserted. - Note that this only runs the* ``{before|after|around}_upsert`` *callbacks.* - - - .. code-block:: ruby - - person = Person.new( - first_name: "Heinrich", - last_name: "Heine" - ) - person.upsert - person.upsert(replace: true) - - * - ``Model#touch`` - - *Update the document's updated_at timestamp, optionally with one extra - provided time field. This will cascade the touch to all* - ``belongs_to`` *associations of the document with the option set. - This operation skips validations and callbacks.* - - *Attempting to touch a destroyed document will raise* ``FrozenError``, - *same as if attempting to update an attribute on a destroyed - document.* - - - .. code-block:: ruby - - person.touch - person.touch(:audited_at) - - * - ``Model#delete`` - - *Deletes the document from the database without running callbacks.* - - *If the document is not persisted, Mongoid will attempt to delete from - the database any document with the same* ``_id``. - - - .. code-block:: ruby - - person.delete - - person = Person.create!(...) - unsaved_person = Person.new(id: person.id) - unsaved_person.delete - person.reload - # raises Mongoid::Errors::DocumentNotFound because the person was deleted - - * - ``Model#destroy`` - - *Deletes the document from the database while running destroy callbacks.* - - *If the document is not persisted, Mongoid will attempt to delete from - the database any document with the same* ``_id``. - - - .. code-block:: ruby - - person.destroy - - person = Person.create!(...) - unsaved_person = Person.new(id: person.id) - unsaved_person.destroy - person.reload - # raises Mongoid::Errors::DocumentNotFound because the person was deleted - - * - ``Model.delete_all`` - - *Deletes all documents from the database without running any callbacks.* - - - .. code-block:: ruby - - Person.delete_all - - * - ``Model.destroy_all`` - - *Deletes all documents from the database while running callbacks. This is a - potentially expensive operation since all documents will be loaded into memory.* - - - .. code-block:: ruby - - Person.destroy_all - -Mongoid provides the following persistence-related attributes: - -.. list-table:: - :header-rows: 1 - :widths: 30 60 - - * - Attribute - - Example - - - * - ``Model#new_record?`` - - *Returns* ``true`` *if the model instance has not yet been saved - to the database. Opposite of* ``persisted?`` - - - .. code-block:: ruby - - person = Person.new( - first_name: "Heinrich", - last_name: "Heine" - ) - person.new_record? # => true - person.save! - person.new_record? # => false - - * - ``Model#persisted?`` - - *Returns* ``true`` *if the model instance has been saved - to the database. Opposite of* ``new_record?`` - - - .. code-block:: ruby - - person = Person.new( - first_name: "Heinrich", - last_name: "Heine" - ) - person.persisted? # => false - person.save! - person.persisted? # => true - - -Atomic ------- - -Mongoid exposes :manual:`MongoDB update operators ` -as methods on Mongoid documents. When these methods are used, callbacks are -not invoked and validations are not performed. The supported update operators -are: - -.. list-table:: - :header-rows: 1 - :widths: 30 60 - - * - Operation - - Example - - * - ``Model#add_to_set`` - - *Performs an atomic $addToSet on the field.* - - - .. code-block:: ruby - - person.add_to_set(aliases: "Bond") - - * - ``Model#bit`` - - *Performs an atomic $bit on the field.* - - - .. code-block:: ruby - - person.bit(age: { and: 10, or: 12 }) - - * - ``Model#inc`` - - *Performs an atomic $inc on the field.* - - - .. code-block:: ruby - - person.inc(age: 1) - - * - ``Model#pop`` - - *Performs an atomic $pop on the field.* - - - .. code-block:: ruby - - person.pop(aliases: 1) - - * - ``Model#pull`` - - *Performs an atomic $pull on the field.* - - - .. code-block:: ruby - - person.pull(aliases: "Bond") - - * - ``Model#pull_all`` - - *Performs an atomic $pullAll on the field.* - - - .. code-block:: ruby - - person.pull_all(aliases: [ "Bond", "James" ]) - - * - ``Model#push`` - - *Performs an atomic $push on the field.* - - - .. code-block:: ruby - - person.push(aliases: ["007","008"]) - - * - ``Model#rename`` - - *Performs an atomic $rename on the field.* - - - .. code-block:: ruby - - person.rename(bday: :dob) - - * - ``Model#set`` - - *Updates an attribute on the model instance and, if the instance - is already persisted, performs an atomic $set on the field, bypassing - validations.* - - ``set`` *can also deeply set values on Hash fields.* - - ``set`` *can also deeply set values on* ``embeds_one`` *associations. - If such an association's document is nil, one will be created prior - to the update.* - - ``set`` *should not be used with* ``has_one`` *associations, as it - does not correctly work in such cases.* - - - - .. code-block:: ruby - - person = Person.create!(name: "Ricky Bobby") - person.set(name: "Tyler Durden") # updates name in the database - - - person = Person.new - person.set(name: "Tyler Durden") # does not write to database - person.name # => "Tyler Durden" - person.persisted? # => true - - - class Post - include Mongoid::Document - - field :metadata, type: Hash - end - - post = Post.create! - post.set('metadata.published_at' => Time.now) - post.metadata['published_at'] # => Time instance - - post.set('metadata.approved.today' => true) - post.metadata['approved'] # => {'today' => true} - - - class Flight - include Mongoid::Document - - embeds_one :plan - end - - class Plan - include Mongoid::Document - - embedded_in :flight - - field :route, type: String - end - - flight = Flight.create! - flight.plan # => nil - flight.set('plan.route', 'test route') - flight.plan # => Plan instance - flight.plan.route # => "test route" - - - * - ``Model#unset`` - - *Performs an atomic $unset on the field.* - - - .. code-block:: ruby - - person.unset(:name) - -Note that, because these methods skip validations, it is possible to both -save invalid documents into the database and end up with invalid documents -in the application (which would subsequently fail to save via a ``save`` -call due to the failing validations). - - -.. _atomic-operation-grouping: - -Atomic Operation Grouping -````````````````````````` - -Atomic operations may be grouped together using the ``#atomically`` method -on a document. All operations inside the block given to ``#atomically`` -are sent to the cluster in a single atomic command. For example: - -.. code-block:: ruby - - person.atomically do - person.inc(age: 1) - person.set(name: 'Jake') - end - -``#atomically`` blocks may be nested. The default behavior is to write -changes performed by each block as soon as the block ends: - -.. code-block:: ruby - - person.atomically do - person.atomically do - person.inc(age: 1) - person.set(name: 'Jake') - end - raise 'An exception' - # name and age changes are still persisted - end - -This behavior can be changed by specifying the ``join_context: true`` option -to ``#atomically``, or globally by setting the ``join_contexts`` -:ref:`configuration option ` to ``true``. When -context joining is enabled, nested ``#atomically`` blocks are joined with -the outer blocks, and only the outermost block (or the first block where -``join_contexts`` is false) actually writes changes to the cluster. -For example: - -.. code-block:: ruby - - person.atomically do - person.atomically(join_context: true) do - person.inc(age: 1) - person.set(name: 'Jake') - end - raise 'An exception' - # name and age changes are not persisted - end - -The context joining behavior can be enabled globally by default by setting -``join_context`` option in Mongoid configuration. In this case specifying -``join_context: false`` on an ``#atomically`` block can be used to -obtain the independent persistence context behavior. - -If an exception is raised in an ``#atomically`` block which has not yet -persisted its changes to the cluster, any pending attribute changes on -Mongoid models are reverted. For example: - -.. code-block:: ruby - - person = Person.new(name: 'Tom') - begin - person.atomically do - person.inc(age: 1) - person.set(name: 'Jake') - person.name # => 'Jake' - raise 'An exception' - end - rescue Exception - person.name # => 'Tom' - end - -Atomic operations described in this section apply to one document at a time, -therefore nesting ``#atomically`` blocks invoked on multiple documents does -not make changes to the different documents be persisted atomically together. -However, MongoDB offers :ref:`multi-document transactions ` -as of server version 4.0 which provide atomic persistence across multiple -documents. - - -Reloading -========= - -Use the ``reload`` method to fetch the most recent version of a document from -the database. Any unsaved modifications to the document's attributes are lost: - -.. code-block:: ruby - - band = Band.create!(name: 'foo') - # => # - - band.name = 'bar' - band - # => # - - band.reload - # => # - -When a document is reloaded, all of its embedded associations are also reloaded -in the same query (since embedded documents are stored in the parent document -on the server). If a document has referenced associations, the loaded -associations' are not reloaded but their values are cleared, such that these -associations would be loaded from the database at the next access. - -.. note:: - - Some operations on associations, for example assignment, persists the new - document. In these cases there may not be any unsaved modifications to - revert by reloading. In the following example, the assignment of the - empty array to the association is immediately persisted and reloading - does not make any changes to the document: - - .. code-block:: ruby - - # Assuming band has many tours, which could be referenced: - band = Band.create!(tours: [Tour.create!]) - # ... or embedded: - band = Band.create!(tours: [Tour.new]) - - # This writes the empty tour list into the database. - band.tours = [] - - # There are no unsaved modifications in band at this point to be reverted. - band.reload - - # Returns the empty array since this is what is in the database. - band.tours - # => [] - -If the model has a :ref:`shard key ` defined, the shard key value -is included in the reloading query. - -If the database does not contain a matching document, Mongoid normally raises -``Mongoid::Errors::DocumentNotFound``. However, if the configuration option -``raise_not_found_error`` is set to ``false``, and the database does not -contain a matching document, Mongoid replaces the current document with a newly -created document whose attributes are set to default values. Importantly, this -generally causes the ``_id`` of the document to change, as the following -example demonstrates: - -.. code-block:: ruby - - band = Band.create! - # => # - - Mongoid.raise_not_found_error = false - band.destroy - - band.reload - # => # - -For this reason, it is not recommended to use ``reload`` when -``raise_not_found_error`` is set to ``false``. - - -Reloading Unsaved Documents ---------------------------- - -``reload`` can be called when the document has not yet been persisted. -In this case ``reload`` performs a ``find`` query using the ``id`` value -specified in the document (and the shard key value, if a shard key is defined): - -.. code-block:: ruby - - existing = Band.create!(name: 'Photek') - - # Unsaved document - band = Band.new(id: existing.id) - band.reload - band.name - # => "Photek" - - -Accessing Field Values -====================== - -Mongoid provides several ways of accessing field values. - -.. note:: - - All of the access methods described below raise - ``Mongoid::Errors::AttributeNotLoaded`` when the field being accessed is - :ref:`projected out `, either by virtue of not being included in - :ref:`only ` or by virtue of being included in - :ref:`without `. This applies to both reads and writes. - - -Getters & Setters ------------------ - -The recommended way is to use the getter and setter methods generated for -each declared field: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - field :first_name - end - - person = Person.new - - person.first_name = "Artem" - person.first_name - # => "Artem" - -To use this mechanism, each field must be explicitly declared, or the -model class must enable :ref:`dynamic fields `. - - -Custom Getters & Setters ------------------------- - -It is possible to explicitly define the getter and setter methods to provide -custom behavior when reading or writing fields, for example value -transformations or storing values under different field names. In this case -``read_attribute`` and ``write_attribute`` methods can be used to read and -write the values directly into the attributes hash: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - def first_name - read_attribute(:fn) - end - - def first_name=(value) - write_attribute(:fn, value) - end - end - - person = Person.new - - person.first_name = "Artem" - person.first_name - # => "Artem" - - person.attributes - # => {"_id"=>BSON::ObjectId('606477dc2c97a628cf47075b'), "fn"=>"Artem"} - -.. note:: - - The custom setters are called during the assignment of - :ref:`nested attributes `, however they are called before - the associations are set up. Because of this, associations may not always - be available during these methods, and it is encouraged to include checks - for their presence whenever referring to them. :ref:`Callbacks ` - can also be used to perform operations on certain events, and associations - will have already been setup and are available during their execution. - -.. _read-write-attribute: - -``read_attribute`` & ``write_attribute`` ----------------------------------------- - -The ``read_attribute`` and ``write_attribute`` methods can be used explicitly -as well. Note that if a field specifies its :ref:`storage field name -`, both ``read_attribute`` and ``write_attribute`` -accept either the declared field name or the storage field name for operations: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - field :first_name, as: :fn - field :last_name, as: :ln - end - - person = Person.new(first_name: "Artem") - # => # - - person.read_attribute(:first_name) - # => "Artem" - - person.read_attribute(:fn) - # => "Artem" - - person.write_attribute(:last_name, "Pushkin") - person - # => # - - person.write_attribute(:ln, "Medvedev") - person - # => # - -``read_attribute`` and ``write_attribute`` do not require that a field with -the used name is defined, but writing field values with ``write_attribute`` -does not cause the respective field to be defined either: - -.. code-block:: ruby - - person.write_attribute(:undefined, "Hello") - person - # => # - person.attributes - # => {"_id"=>BSON::ObjectId('60647b212c97a6292c195b4c'), "first_name"=>"Artem", "last_name"=>"Medvedev", "undefined"=>"Hello"} - - person.read_attribute(:undefined) - # => "Hello" - person.undefined - # raises NoMethodError - -When ``read_attribute`` is used to access a missing field, it returns ``nil``. - - -Hash Access ------------ - -Mongoid model instances define the ``[]`` and ``[]=`` methods to provide -``Hash`` style access to the attributes. ``[]`` is an alias for -``read_attribute`` and ``[]=`` is an alias for ``write_attribute``; see -the section on :ref:`read_attribute and write_attribute ` -for the detailed description of their behavior. - -.. code-block:: ruby - - class Person - include Mongoid::Document - - field :first_name, as: :fn - field :last_name, as: :ln - end - - person = Person.new(first_name: "Artem") - - person["fn"] - # => "Artem" - - person[:first_name] - # => "Artem" - - person[:ln] = "Medvedev" - person - # => # - - person["last_name"] = "Pushkin" - person - # => # - - -Bulk Attribute Writes ---------------------- - -In cases where you want to set multiple field values at once, there are a few -different ways of accomplishing this as well. - -.. code-block:: ruby - - # Get the field values as a hash. - person.attributes - - # Set the field values in the document. - Person.new(first_name: "Jean-Baptiste", middle_name: "Emmanuel") - person.attributes = { first_name: "Jean-Baptiste", middle_name: "Emmanuel" } - person.write_attributes( - first_name: "Jean-Baptiste", - middle_name: "Emmanuel", - ) - - -Dirty Tracking -============== - -Mongoid supports tracking of changed or "dirty" fields with an API that mirrors that of -Active Model. If a defined field has been modified in a model the model will be marked as -dirty and some additional behavior comes into play. - - -Viewing Changes ---------------- - -There are various ways to view what has been altered on a model. Changes are recorded -from the time a document is instantiated, either as a new document or via loading from -the database up to the time it is saved. Any persistence operation clears the changes. - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :name, type: String - end - - person = Person.first - person.name = "Alan Garner" - - # Check to see if the document has changed. - person.changed? # true - - # Get an array of the names of the changed fields. - person.changed # [ :name ] - - # Get a hash of the old and changed values for each field. - person.changes # { "name" => [ "Alan Parsons", "Alan Garner" ] } - - # Check if a specific field has changed. - person.name_changed? # true - - # Get the changes for a specific field. - person.name_change # [ "Alan Parsons", "Alan Garner" ] - - # Get the previous value for a field. - person.name_was # "Alan Parsons" - -.. note:: - - Setting the associations on a document does not cause the ``changes`` or - ``changed_attributes`` hashes to be modified. This is true for all associations - whether referenced or embedded. Note that changing the _id(s) field on - referenced associations does cause the changes to show up in the ``changes`` - and the ``changed_attributes`` hashes. - - -Resetting Changes ------------------ - -You can reset changes of a field to its previous value by calling the reset method. - -.. code-block:: ruby - - person = Person.first - person.name = "Alan Garner" - - # Reset the changed name back to the original - person.reset_name! - person.name # "Alan Parsons" - - -Persistence ------------ - -Mongoid uses dirty tracking as the core of its persistence operations. It looks at the -changes on a document and atomically updates only what has changed, unlike other frameworks -that write the entire document on each save. If no changes have been made, Mongoid will -not hit the database on a call to ``Model#save``. - - -Viewing Previous Changes ------------------------- - -After a document has been persisted, you can see what the changes were previously by -calling ``Model#previous_changes``. - -.. code-block:: ruby - - person = Person.first - person.name = "Alan Garner" - person.save # Clears out current changes. - - # View the previous changes. - person.previous_changes # { "name" => [ "Alan Parsons", "Alan Garner" ] } - - -Updating Container Fields -========================= - -Be aware that, until -`MONGOID-2951 `_ -is resolved, all fields including container ones must be assigned to for -their values to be persisted to the database. - -For example, adding to a set like this does not work: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :tours, type: Set - end - - band = Band.new - band.tours - # => # - - band.tours << 'London' - # => # - band.tours - # => # - -Instead, the field value must be modified outside of the model and assigned -back to the model as follows: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :tours, type: Set - end - - band = Band.new - - tours = band.tours - # => # - - tours << 'London' - # => # - - band.tours = tours - # => # - - band.tours - # => # - - -.. _readonly-documents: - -Readonly Documents -================== - -Documents can be marked read-only in two ways, depending on the value of the -``Mongoid.legacy_readonly`` feature flag: - -If this flag is turned off, a document is marked read-only when the ``#readonly!`` -method is called on that documnet. A read-only document, with this flag turned off, -will raise a ReadonlyDocument error on attempting to perform any persistence -operation, including (but not limited to) saving, updating, deleting and -destroying. Note that reloading does not reset the read-only state. - -.. code:: ruby - - band = Band.first - band.readonly? # => false - band.readonly! - band.readonly? # => true - band.name = "The Rolling Stones" - band.save # => raises ReadonlyDocument error - band.reload.readonly? # => true - -If this flag is turned on, a document is marked read-only when that document -has been projected (i.e. using ``#only`` or ``#without``). A read-only document, -with this flag turned on, will not be deletable or destroyable (a -``ReadonlyDocument`` error will be raised), but will be saveable and updatable. -The read-only status is reset on reloading the document. - -.. code:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :genre, type: String - end - - band = Band.only(:name).first - band.readonly? # => true - band.destroy # => raises ReadonlyDocument error - band.reload.readonly? # => false - - -Overriding ``readonly?`` ------------------------- - -Another way to make a document read-only is by overriding the readonly? method: - -.. code:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :genre, type: String - - def readonly? - true - end - end - - band = Band.first - band.readonly? # => true - band.destroy # => raises ReadonlyDocument error diff --git a/source/reference/fields.txt b/source/reference/fields.txt deleted file mode 100644 index 6198bb6c..00000000 --- a/source/reference/fields.txt +++ /dev/null @@ -1,1776 +0,0 @@ -.. _fields: - -**************** -Field Definition -**************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -.. _field-types: - -Field Types -=========== - -MongoDB stores underlying document data using -`BSON types `_, and -Mongoid converts BSON types to Ruby types at runtime in your application. -For example, a field defined with ``type: :float`` will use the Ruby ``Float`` -class in-memory and will persist in the database as the the BSON ``double`` type. - -Field type definitions determine how Mongoid behaves when constructing queries -and retrieving/writing fields from/to the database. Specifically: - -1. When assigning values to fields at runtime, the values are converted to the - specified type. -2. When persisting data to MongoDB, the data is sent in an appropriate - type, permitting richer data manipulation within MongoDB or by other - tools. -3. When querying documents, query parameters are converted to the specified - type before being sent to MongoDB. -4. When retrieving documents from the database, field values are converted - to the specified type. - -Changing the field definitions in a model class does not alter data already stored in -MongoDB. To update type or contents of fields of existing documents, -the field must be re-saved to the database. Note that, due to Mongoid -tracking which attributes on a model change and only saving the changed ones, -it may be necessary to explicitly write a field value when changing the -type of an existing field without changing the stored values. - -Consider a simple class for modeling a person in an application. A person may -have a name, date_of_birth, and weight. We can define these attributes -on a person by using the ``field`` macro. - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :name, type: String - field :date_of_birth, type: Date - field :weight, type: Float - end - -The valid types for fields are as follows: - -- ``Array`` -- ``BSON::Binary`` -- :ref:`BigDecimal ` -- ``Mongoid::Boolean``, which may be specified simply as ``Boolean`` in the - scope of a class which included ``Mongoid::Document``. -- :ref:`Date ` -- :ref:`DateTime ` -- ``Float`` -- :ref:`Hash ` -- ``Integer`` -- :ref:`Object ` -- ``BSON::ObjectId`` -- ``Range`` -- :ref:`Regexp ` -- ``Set`` -- ``String`` -- :ref:`Mongoid::StringifiedSymbol `, - which may be specified simply as ``StringifiedSymbol`` in the scope of a - class which included ``Mongoid::Document``. -- :ref:`Symbol ` -- :ref:`Time ` -- ``ActiveSupport::TimeWithZone`` - -Mongoid also recognizes the string ``"Boolean"`` as an alias for the -``Mongoid::Boolean`` class. - -To define custom field types, refer to :ref:`Custom Field Types ` below. - -.. note:: - - Using the ``BSON::Int64`` and ``BSON::Int32`` types as field types is unsupported. - Saving these types to the database will work as expected, however, querying them - will return the native Ruby ``Integer`` type. Querying fields of type - ``BSON::Decimal128`` will return values of type ``BSON::Decimal128`` in - BSON <=4 and values of type ``BigDecimal`` in BSON 5+. - - -.. _untyped-fields: - -Untyped Fields --------------- - -Not specifying a type for a field is the same as specifying the ``Object`` -type. Such fields are untyped: - -.. code-block:: ruby - - class Product - include Mongoid::Document - - field :properties - # Equivalent to: - field :properties, type: Object - end - -An untyped field can store values of any type which is directly serializable -to BSON. This is useful when a field may contain values of different types -(i.e. it is a variant type field), or when the type of values is not known -ahead of time: - -.. code-block:: ruby - - product = Product.new(properties: "color=white,size=large") - product.properties - # => "color=white,size=large" - - product = Product.new(properties: {color: "white", size: "large"}) - product.properties - # => {:color=>"white", :size=>"large"} - -When values are assigned to the field, Mongoid still performs mongoization but -uses the class of the value rather than the field type for mongoization logic. - -.. code-block:: ruby - - product = Product.new(properties: 0..10) - product.properties - # The range 0..10, mongoized: - # => {"min"=>0, "max"=>10} - -When reading data from the database, Mongoid does not perform any type -conversions on untyped fields. For this reason, even though it is possible -to write any BSON-serializable value into an untyped fields, values which -require special handling on the database reading side will generally not work -correctly in an untyped field. Among field types supported by Mongoid, -values of the following types should not be stored in untyped fields: - -- ``Date`` (values will be returned as ``Time``) -- ``DateTime`` (values will be returned as ``Time``) -- ``Range`` (values will be returned as ``Hash``) - - -.. _field-type-stringified-symbol: - -Field Type: StringifiedSymbol ------------------------------ - -The ``StringifiedSymbol`` field type is the recommended field type for storing -values that should be exposed as symbols to Ruby applications. When using the ``Symbol`` field type, -Mongoid defaults to storing values as BSON symbols. For more information on the -BSON symbol type, see :ref:`here `. -However, the BSON symbol type is deprecated and is difficult to work with in programming languages -without native symbol types, so the ``StringifiedSymbol`` type allows the use of symbols -while ensuring interoperability with other drivers. The ``StringifiedSymbol`` type stores all data -on the database as strings, while exposing values to the application as symbols. - -An example usage is shown below: - -.. code-block:: ruby - - class Post - include Mongoid::Document - - field :status, type: StringifiedSymbol - end - - post = Post.new(status: :hello) - # status is stored as "hello" on the database, but returned as a Symbol - post.status - # => :hello - - # String values can be assigned also: - post = Post.new(status: "hello") - # status is stored as "hello" on the database, but returned as a Symbol - post.status - # => :hello - -All non-string values will be stringified upon being sent to the database (via ``to_s``), and -all values will be converted to symbols when returned to the application. Values that cannot be -converted directly to symbols, such as integers and arrays, will first be converted to strings and -then symbols before being returned to the application. - -For example, setting an integer as ``status``: - -.. code-block:: ruby - - post = Post.new(status: 42) - post.status - # => :"42" - -If the ``StringifiedSymbol`` type is applied to a field that contains BSON symbols, the values -will be stored as strings instead of BSON symbols on the next save. This permits transparent lazy -migration from fields that currently store either strings or BSON symbols in the database to the -``StringifiedSymbol`` field type. - - -.. _field-type-symbol: - -Field Type: Symbol ------------------- - -New applications should use the :ref:`StringifiedSymbol field type ` -to store Ruby symbols in the database. The ``StringifiedSymbol`` field type -provides maximum compatibility with other applications and programming languages -and has the same behavior in all circumstances. - -Mongoid also provides the deprecated ``Symbol`` field type for serializing -Ruby symbols to BSON symbols. Because the BSON specification deprecated the -BSON symbol type, the ``bson`` gem will serialize Ruby symbols into BSON strings -when used on its own. However, in order to maintain backwards compatibility -with older datasets, the ``mongo`` gem overrides this behavior to serialize Ruby -symbols as BSON symbols. This is necessary to be able to specify queries for -documents which contain BSON symbols as fields. - -To override the default behavior and configure the ``mongo`` gem (and thereby -Mongoid as well) to encode symbol values as strings, include the following code -snippet in your project: - -.. code-block:: ruby - - class Symbol - def bson_type - BSON::String::BSON_TYPE - end - end - - -.. _field-type-hash: - -Field Type: Hash ----------------- - -When using a field of type Hash, be wary of adhering to the -`legal key names for mongoDB `_, -or else the values will not store properly. - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :first_name - field :url, type: Hash - - # will update the fields properly and save the values - def set_vals - self.first_name = 'Daniel' - self.url = {'home_page' => 'http://www.homepage.com'} - save - end - - # all data will fail to save due to the illegal hash key - def set_vals_fail - self.first_name = 'Daniel' - self.url = {'home.page' => 'http://www.homepage.com'} - save - end - end - - -.. _field-type-time: - -Field Type: Time ----------------- - -``Time`` fields store values as ``Time`` instances in the :ref:`configured -time zone `. - -``Date`` and ``DateTime`` instances are converted to ``Time`` instances upon -assignment to a ``Time`` field: - -.. code-block:: ruby - - class Voter - include Mongoid::Document - - field :registered_at, type: Time - end - - Voter.new(registered_at: Date.today) - # => # - -In the above example, the value was interpreted as the beginning of today in -local time, because the application was not configured to use UTC times. - -.. note:: - - When the database contains a string value for a ``Time`` field, Mongoid - parses the string value using ``Time.parse`` which considers values without - time zones to be in local time. - - -.. _field-type-date: - -Field Type: Date ----------------- - -Mongoid allows assignment of values of several types to ``Date`` fields: - -- ``Date`` - the provided date is stored as is. -- ``Time``, ``DateTime``, ``ActiveSupport::TimeWithZone`` - the date component - of the value is taken in the value's time zone. -- ``String`` - the date specified in the string is used. -- ``Integer``, ``Float`` - the value is taken to be a UTC timestamp which is - converted to the :ref:`configured time zone ` (note that - ``Mongoid.use_utc`` has no effect on this conversion), then the date is - taken from the resulting time. - -In other words, if a date is specified in the value, that date is used without -first converting the value to the configured time zone. - -As a date & time to date conversion is lossy (it discards the time component), -especially if an application operates with times in different time zones it is -recommended to explicitly convert ``String``, ``Time`` and ``DateTime`` -objects to ``Date`` objects before assigning the values to fields of type -``Date``. - -.. note:: - - When the database contains a string value for a ``Date`` field, Mongoid - parses the string value using ``Time.parse``, discards the time portion of - the resulting ``Time`` object and uses the date portion. ``Time.parse`` - considers values without time zones to be in local time. - - -.. _field-type-date-time: - -Field Type: DateTime ---------------------- - -MongoDB stores all times as UTC timestamps. When assigning a value to a -``DateTime`` field, or when querying a ``DateTime`` field, Mongoid -converts the passed in value to a UTC ``Time`` before sending it to the -MongoDB server. - -``Time``, ``ActiveSupport::TimeWithZone`` and ``DateTime`` objects embed -time zone information, and the value persisted is the specified moment in -time, in UTC. When the value is retrieved, the time zone in which it is -returned is defined by the :ref:`configured time zone settings `. - -.. code-block:: ruby - - class Ticket - include Mongoid::Document - field :opened_at, type: DateTime - end - - Time.zone = 'Berlin' - - ticket = Ticket.create!(opened_at: '2018-02-18 07:00:08 -0500') - - ticket.opened_at - # => Sun, 18 Feb 2018 13:00:08 +0100 - ticket - # => # - - Time.zone = 'America/New_York' - ticket.opened_at - # => Sun, 18 Feb 2018 07:00:08 -0500 - - Mongoid.use_utc = true - ticket.opened_at - # => Sun, 18 Feb 2018 12:00:08 +0000 - -Mongoid also supports casting integers and floats to ``DateTime``. When -doing so, the integers/floats are assumed to be Unix timestamps (in UTC): - -.. code-block:: ruby - - ticket.opened_at = 1544803974 - ticket.opened_at - # => Fri, 14 Dec 2018 16:12:54 +0000 - -If a string is used as a ``DateTime`` field value, the behavior depends on -whether the string includes a time zone. If no time zone is specified, -the :ref:`default Mongoid time zone ` is used: - -.. code-block:: ruby - - Time.zone = 'America/New_York' - ticket.opened_at = 'Mar 4, 2018 10:00:00' - ticket.opened_at - # => Sun, 04 Mar 2018 15:00:00 +0000 - -If a time zone is specified, it is respected: - -.. code-block:: ruby - - ticket.opened_at = 'Mar 4, 2018 10:00:00 +01:00' - ticket.opened_at - # => Sun, 04 Mar 2018 09:00:00 +0000 - -.. note:: - - When the database contains a string value for a ``DateTime`` field, Mongoid - parses the string value using ``Time.parse`` which considers values without - time zones to be in local time. - - -.. _field-type-regexp: - -Field Type: Regexp ------------------- - -MongoDB supports storing regular expressions in documents, and querying using -regular expressions. Note that MongoDB uses -`Perl-compatible regular expressions (PCRE) `_ -and Ruby uses `Onigmo `_, which is a -fork of `Oniguruma regular expression engine `_. -The two regular expression implementations generally provide equivalent -functionality but have several important syntax differences. - -When a field is declared to be of type Regexp, Mongoid converts Ruby regular -expressions to BSON regular expressions and stores the result in MongoDB. -Retrieving the field from the database produces a ``BSON::Regexp::Raw`` -instance: - -.. code-block:: ruby - - class Token - include Mongoid::Document - - field :pattern, type: Regexp - end - - token = Token.create!(pattern: /hello.world/m) - token.pattern - # => /hello.world/m - - token.reload - token.pattern - # => # - -Use ``#compile`` method on ``BSON::Regexp::Raw`` to get back the Ruby regular -expression: - -.. code-block:: ruby - - token.pattern.compile - # => /hello.world/m - -Note that, if the regular expression was not originally a Ruby one, calling -``#compile`` on it may produce a different regular expression. For example, -the following is a PCRE matching a string that ends in "hello": - -.. code-block:: ruby - - BSON::Regexp::Raw.new('hello$', 's') - # => # - -Compiling this regular expression produces a Ruby regular expression that -matches strings containing "hello" before a newline, besides strings ending in -"hello": - -.. code-block:: ruby - - BSON::Regexp::Raw.new('hello$', 's').compile =~ "hello\nworld" - # => 0 - -This is because the meaning of ``$`` is different between PCRE and Ruby -regular expressions. - -.. _field-type-big-decimal: - -BigDecimal Fields ------------------ - -The ``BigDecimal`` field type is used to store numbers with increased precision. - -The ``BigDecimal`` field type stores its values in two different ways in the -database, depending on the value of the ``Mongoid.map_big_decimal_to_decimal128`` -global config option. If this flag is set to false (which is the default), -the ``BigDecimal`` field will be stored as a string, otherwise it will be stored -as a ``BSON::Decimal128``. - -The ``BigDecimal`` field type has some limitations when converting to and from -a ``BSON::Decimal128``: - -- ``BSON::Decimal128`` has a limited range and precision, while ``BigDecimal`` - has no restrictions in terms of range and precision. ``BSON::Decimal128`` has - a max value of approximately ``10^6145`` and a min value of approximately - ``-10^6145``, and has a maximum of 34 bits of precision. When attempting to - store values that don't fit into a ``BSON::Decimal128``, it is recommended to - have them stored as a string instead of a ``BSON::Decimal128``. You can do - that by setting ``Mongoid.map_big_decimal_to_decimal128`` to ``false``. If a - value that does not fit in a ``BSON::Decimal128`` is attempted to be stored - as one, an error will be raised. - -- ``BSON::Decimal128`` is able to accept signed ``NaN`` values, while - ``BigDecimal`` is not. When retrieving signed ``NaN`` values from - the database using the ``BigDecimal`` field type, the ``NaN`` will be - unsigned. - -- ``BSON::Decimal128`` maintains trailing zeroes when stored in the database. - ``BigDecimal``, however, does not maintain trailing zeroes, and therefore - retrieving ``BSON::Decimal128`` values using the ``BigDecimal`` field type - may result in a loss of precision. - -There is an additional caveat when storing a ``BigDecimal`` in a field with no -type (i.e. a dynamically typed field) and ``Mongoid.map_big_decimal_to_decimal128`` -is ``false``. In this case, the ``BigDecimal`` is stored as a string, and since a -dynamic field is being used, querying for that field with a ``BigDecimal`` will -not find the string for that ``BigDecimal``, since the query is looking for a -``BigDecimal``. In order to query for that string, the ``BigDecimal`` must -first be converted to a string with ``to_s``. Note that this is not a problem -when the field has type ``BigDecimal``. - -If you wish to avoid using ``BigDecimal`` altogether, you can set the field -type to ``BSON::Decimal128``. This will allow you to keep track of trailing -zeroes and signed ``NaN`` values. - -Migration to ``decimal128``-backed ``BigDecimal`` Field -``````````````````````````````````````````````````````` -In a future major version of Mongoid, the ``Mongoid.map_big_decimal_to_decimal128`` -global config option will be defaulted to ``true``. When this flag is turned on, -``BigDecimal`` values in queries will not match to the strings that are already -stored in the database; they will only match to ``decimal128`` values that are -in the database. If you have a ``BigDecimal`` field that is backed by strings, -you have three options: - -1. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be - set to ``false``, and you can continue storing your ``BigDecimal`` values as - strings. Note that you are surrendering the advantages of storing ``BigDecimal`` - values as a ``decimal128``, like being able to do queries and aggregations - based on the numerical value of the field. - -2. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be - set to ``true``, and you can convert all values for that field from strings to - ``decimal128`` values in the database. You should do this conversion before - setting the global config option to true. An example query to accomplish this - is as follows: - - .. code-block:: javascript - - db.bands.updateMany({ - "field": { "$exists": true } - }, [ - { - "$set": { - "field": { "$toDecimal": "$field" } - } - } - ]) - - This query updates all documents that have the given field, setting that - field to its corresponding ``decimal128`` value. Note that this query only - works in MongoDB 4.2+. - -3. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be - set to ``true``, and you can have both strings and ``decimal128`` values for - that field. This way, only ``decimal128`` values will be inserted into and - updated to the database going forward. Note that you still don't get the - full advantages of using only ``decimal128`` values, but your dataset is - slowly migrating to all ``decimal128`` values, as old string values are - updated to ``decimal128`` and new ``decimal128`` values are added. With this - setup, you can still query for ``BigDecimal`` values as follows: - - .. code-block:: ruby - - Mongoid.map_big_decimal_to_decimal128 = true - big_decimal = BigDecimal('2E9') - Band.in(sales: [big_decimal, big_decimal.to_s]).to_a - - This query will find all values that are either a ``decimal128`` value or - a string that match that value. - - -Using Symbols Or Strings Instead Of Classes -------------------------------------------- - -Mongoid permits using symbols or strings instead of classes to specify the -type of fields, for example: - -.. code-block:: ruby - - class Order - include Mongoid::Document - - field :state, type: :integer - # Equivalent to: - field :state, type: "integer" - # Equivalent to: - field :state, type: Integer - end - -Only standard field types as listed below can be specified using symbols or -strings in this manner. Mongoid recognizes the following expansions: - -- ``:array`` => ``Array`` -- ``:big_decimal`` => ``BigDecimal`` -- ``:binary`` => ``BSON::Binary`` -- ``:boolean`` => ``Mongoid::Boolean`` -- ``:date`` => ``Date`` -- ``:date_time`` => ``DateTime`` -- ``:float`` => ``Float`` -- ``:hash`` => ``Hash`` -- ``:integer`` => ``Integer`` -- ``:object_id`` => ``BSON::ObjectId`` -- ``:range`` => ``Range`` -- ``:regexp`` => ``Regexp`` -- ``:set`` => ``Set`` -- ``:string`` => ``String`` -- ``:stringified_symbol`` => ``StringifiedSymbol`` -- ``:symbol`` => ``Symbol`` -- ``:time`` => ``Time`` - - -.. _field-default-values: - -Specifying Field Default Values -------------------------------- - -A field can be configured to have a default value. The default value can be -fixed, as in the following example: - -.. code-block:: ruby - - class Order - include Mongoid::Document - - field :state, type: String, default: 'created' - end - -The default value can also be specified as a ``Proc``: - -.. code-block:: ruby - - class Order - include Mongoid::Document - - field :fulfill_by, type: Time, default: ->{ Time.now + 3.days } - end - -.. note:: - - Default values that are not ``Proc`` instances are evaluated at class load - time, meaning the following two definitions are not equivalent: - - .. code-block:: ruby - - field :submitted_at, type: Time, default: Time.now - field :submitted_at, type: Time, default: ->{ Time.now } - - The second definition is most likely the desired one, which causes the - time of submission to be set to the current time at the moment of - document instantiation. - -To set a default which depends on the document's state, use ``self`` -inside the ``Proc`` instance which would evaluate to the document instance -being operated on: - -.. code-block:: ruby - - field :fulfill_by, type: Time, default: ->{ - # Order should be fulfilled in 2 business hours. - if (7..8).include?(self.submitted_at.hour) - self.submitted_at + 4.hours - elsif (9..3).include?(self.submitted_at.hour) - self.submitted_at + 2.hours - else - (self.submitted_at + 1.day).change(hour: 11) - end - } - -When defining a default value as a ``Proc``, Mongoid will apply the default -after all other attributes are set and associations are initialized. -To have the default be applied before the other attributes are set, -use the ``pre_processed: true`` field option: - -.. code-block:: ruby - - field :fulfill_by, type: Time, default: ->{ Time.now + 3.days }, - pre_processed: true - -The ``pre_processed: true`` option is also necessary when specifying a custom -default value via a ``Proc`` for the ``_id`` field, to ensure the ``_id`` -is set correctly via associations: - -.. code-block:: ruby - - field :_id, type: String, default: -> { 'hello' }, pre_processed: true - - -.. _storage-field-names: - -Specifying Storage Field Names ------------------------------- - -One of the drawbacks of having a schemaless database is that MongoDB must -store all field information along with every document, meaning that it -takes up a lot of storage space in RAM and on disk. A common pattern to limit -this is to alias fields to a small number of characters, while keeping the -domain in the application expressive. Mongoid allows you to do this and -reference the fields in the domain via their long names in getters, setters, -and criteria while performing the conversion for you. - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :n, as: :name, type: String - end - - band = Band.new(name: "Placebo") - band.attributes # { "n" => "Placebo" } - - criteria = Band.where(name: "Placebo") - criteria.selector # { "n" => "Placebo" } - -.. _mongoid-field-aliases: - -Field Aliases -------------- - -It is possible to define field aliases. The value will be stored in the -destination field but can be accessed from either the destination field or -from the aliased field: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, type: String - alias_attribute :n, :name - end - - band = Band.new(n: 'Astral Projection') - # => # - - band.attributes - # => {"_id"=>BSON::ObjectId('5fc1c1ee2c97a64accbeb5e1'), "name"=>"Astral Projection"} - - band.n - # => "Astral Projection" - -Aliases can be removed from model classes using the ``unalias_attribute`` -method. - -.. code-block:: ruby - - class Band - unalias_attribute :n - end - -.. _unalias-id: - -Unaliasing ``id`` -````````````````` - -``unalias_attribute`` can be used to remove the predefined ``id`` alias. -This is useful for storing different values in ``id`` and ``_id`` fields: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - unalias_attribute :id - field :id, type: String - end - - Band.new(id: '42') - # => # - - -Reserved Names --------------- - -Attempting to define a field on a document that conflicts with a reserved -method name in Mongoid will raise an error. The list of reserved names can -be obtained by invoking the ``Mongoid.destructive_fields`` method. - - -Field Redefinition ------------------- - -By default Mongoid allows redefining fields on a model. To raise an error -when a field is redefined, set the ``duplicate_fields_exception`` -:ref:`configuration option ` to ``true``. - -With the option set to true, the following example will raise an error: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - field :name - - field :name, type: String - end - -To define the field anyway, use the ``overwrite: true`` option: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - field :name - - field :name, type: String, overwrite: true - end - - -.. _custom-id: - -Custom IDs ----------- - -By default, Mongoid defines the ``_id`` field on documents to contain a -``BSON::ObjectId`` value which is automatically generated by Mongoid. - -It is possible to replace the ``_id`` field definition to change the type -of the ``_id`` values or have different default values: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :_id, type: String, default: ->{ name } - end - -It is possible to omit the default entirely: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :_id, type: String - end - -If the default on ``_id`` is omitted, and no ``_id`` value is provided by -your application, Mongoid will persist the document without the ``_id`` -value. In this case, if the document is a top-level document, an ``_id`` -value will be assigned by the server; if the document is an embedded document, -no ``_id`` value will be assigned. Mongoid will not automatically retrieve -this value, if assigned, when the document is persisted - you -must obtain the persisted value (and the complete persisted document) using -other means: - -.. code-block:: ruby - - band = Band.create! - => # - band.id - => nil - band.reload - # raises Mongoid::Errors::DocumentNotFound - Band.last - => # - -Omitting ``_id`` fields is more common in :ref:`embedded documents `. - -Mongoid also defines the ``id`` field aliased to ``_id``. The ``id`` -alias can :ref:`be removed ` if desired (such as to integrate -with systems that use the ``id`` field to store value different from ``_id``. - -.. _uncastable-values: - -Uncastable Values ------------------ - -In Mongoid 8, Mongoid has standardized the treatment of the assignment and -reading of "uncastable" values. A value is considered "uncastable" when it -cannot be coerced to the type of its field. For example, an array would be an -"uncastable" value to an Integer field. - - -Assigning Uncastable Values -``````````````````````````` - -The assignment of uncastable values has been standardized to assign ``nil`` by -default. Consider the following example: - -.. code:: - - class User - include Mongoid::Document - - field :name, type: Integer - end - - User.new(name: [ "hello" ]) - -Assigning an array to a field of type Integer doesn't work since an array can't -be coerced to an Integer. The assignment of uncastable values to a field will -cause a ``nil`` to be written: - -.. code:: - - user = User.new(name: [ "Mike", "Trout" ]) - # => # - -Note that the original uncastable values will be stored in the -``attributes_before_type_cast`` hash with their field names: - -.. code:: - - user.attributes_before_type_cast["name"] - # => ["Mike", "Trout"] - -.. note:: - - Note that for numeric fields, any class that defines ``to_i`` for Integer - fields, ``to_f`` for Floats, and ``to_d`` for BigDecimals, is castable. - Strings are the exception and will only call the corresponding ``to_*`` - method if the string is numeric. If a class only defines ``to_i`` and not - ``to_f`` and is being assigned to a Float field, this is uncastable, and Mongoid - will not perform a two-step conversion (i.e. ``to_i`` and then ``to_f``). - - -Reading Uncastable Values -````````````````````````` - -When documents in the database contain values of different types than their -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: - -.. code:: - - class User - include Mongoid::Document - - field :name, type: Integer - end - -.. code:: - - { _id: ..., name: [ "Mike", "Trout" ] } - -Reading this document from the database will result in the model's name field -containing ``nil``: - -.. code:: - - User.first.name - # => nil - -The database value of type array cannot be stored in the attribute, since the -array can't be coerced to an Integer. Note that the original uncastable values -will be stored in the ``attributes_before_type_cast`` hash with their field -names: - -.. code:: - - user.attributes_before_type_cast["name"] - # => ["Mike", "Trout"] - -.. note:: - - The ``demongoize`` methods on container objects (i.e. Hash, Array) have not - been changed to permit automatic persistence of mutated container attributes. - See `MONGOID-2951 `_ for a - longer discussion of this topic. - - -.. _customizing-field-behavior: - -Customizing Field Behavior -========================== - -Mongoid offers several ways to customize the behavior of fields. - - -.. _custom-getters-and-setters: - -Custom Getters And Setters --------------------------- - -You may override getters and setters for fields to modify the values -when they are being accessed or written. The getters and setters use the -same name as the field. Use ``read_attribute`` and ``write_attribute`` -methods inside the getters and setters to operate on the raw attribute -values. - -For example, Mongoid provides the ``:default`` field option to write a -default value into the field. If you wish to have a field default value -in your application but do not wish to persist it, you can override the -getter as follows: - -.. code-block:: ruby - - class DistanceMeasurement - include Mongoid::Document - - field :value, type: Float - field :unit, type: String - - def unit - read_attribute(:unit) || "m" - end - - def to_s - "#{value} #{unit}" - end - end - - measurement = DistanceMeasurement.new(value: 2) - measurement.to_s - # => "2.0 m" - measurement.attributes - # => {"_id"=>BSON::ObjectId('613fa0b0a15d5d61502f3447'), "value"=>2.0} - -To give another example, a field which converts empty strings to nil values -may be implemented as follows: - -.. code-block:: ruby - - class DistanceMeasurement - include Mongoid::Document - - field :value, type: Float - field :unit, type: String - - def unit=(value) - if value.blank? - value = nil - end - write_attribute(:unit, value) - end - end - - measurement = DistanceMeasurement.new(value: 2, unit: "") - measurement.attributes - # => {"_id"=>BSON::ObjectId('613fa15aa15d5d617216104c'), "value"=>2.0, "unit"=>nil} - - -.. _custom-field-types: - -Custom Field Types ------------------- - -You can define custom types in Mongoid and determine how they are serialized -and deserialized. In this example, we define a new field type ``Point``, which we -can use in our model class as follows: - -.. code-block:: ruby - - class Profile - include Mongoid::Document - field :location, type: Point - end - -Then make a Ruby class to represent the type. This class must define methods -used for MongoDB serialization and deserialization as follows: - -.. code-block:: ruby - - class Point - - attr_reader :x, :y - - def initialize(x, y) - @x, @y = x, y - end - - # Converts an object of this instance into a database friendly value. - # In this example, we store the values in the database as array. - def mongoize - [ x, y ] - end - - class << self - - # Takes any possible object and converts it to how it would be - # stored in the database. - def mongoize(object) - case object - when Point then object.mongoize - when Hash then Point.new(object[:x], object[:y]).mongoize - else object - end - end - - # Get the object as it was stored in the database, and instantiate - # this custom class from it. - def demongoize(object) - Point.new(object[0], object[1]) - end - - # Converts the object that was supplied to a criteria and converts it - # into a query-friendly form. - def evolve(object) - case object - when Point then object.mongoize - else object - end - end - end - end - -The instance method ``mongoize`` takes an instance of your custom type object, and -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 ]``. - -The class method ``mongoize`` is similar to the instance method, however it must handle -objects of all possible types as inputs. The ``mongoize`` method is used when calling the -setter methods for fields of your custom type. - -.. code-block:: ruby - - point = Point.new(12, 24) - venue = Venue.new(location: point) # This uses the Point#mongoize instance method. - venue = Venue.new(location: [ 12, 24 ]) # This uses the Point.mongoize class method. - -The class method ``demongoize`` does the inverse of ``mongoize``. It takes the raw object -from the MongoDB Ruby driver and converts it to an instance of your custom type. -In this case, the database driver returns an ``Array`` and we instantiate a ``Point`` from it. -The ``demongoize`` method is used when calling the getters of fields for your custom type. -Note that in the example above, since ``demongoize`` calls ``Point.new``, a new instance of -``Point`` will be generated on each call to the getter. - -Mongoid will always call the ``demongoize`` method on values that were -retrieved from the database, but applications may, in theory, call -``demongoize`` with arbitrary input. It is recommended that applications add -handling for arbitrary input in their ``demongoize`` methods. We can rewrite -``Point``'s demongoize method as follows: - -.. code:: ruby - - def demongoize(object) - if object.is_a?(Array) && object.length == 2 - Point.new(object[0], object[1]) - end - end - -Notice that ``demongoize`` will only create a new ``Point`` if given an array -of length 2, and will return ``nil`` otherwise. Both the ``mongoize`` and -``demongoize`` methods should be prepared to receive arbitrary input and should -return ``nil`` on values that are uncastable to your custom type. See the -section on :ref:`Uncastable Values ` for more details. - -Lastly, the class method ``evolve`` is similar to ``mongoize``, however it is used -when transforming objects for use in Mongoid query criteria. - -.. code-block:: ruby - - point = Point.new(12, 24) - Venue.where(location: point) # This uses Point.evolve - -The ``evolve`` method should also be prepared to receive arbitrary input, -however, unlike the ``mongoize`` and ``demongoize`` methods, it should return -the inputted value on values that are uncastable to your custom type. See the -section on :ref:`Uncastable Values ` for more details. - - -.. _phantom-custom-field-types: - -Phantom Custom Field Types -`````````````````````````` - -The custom field type may perform conversions from user-visible attribute -values to the values stored in the database when the user-visible attribute -value type is different from the declared field type. For example, this -can be used to implement a mapping from one enumeration to another, to -have more descriptive values in the application and more compact values stored -in the database: - -.. code-block:: ruby - - class ColorMapping - - MAPPING = { - 'black' => 0, - 'white' => 1, - }.freeze - - INVERSE_MAPPING = MAPPING.invert.freeze - - class << self - - # Takes application-scope value and converts it to how it would be - # stored in the database. Converts invalid values to nil. - def mongoize(object) - MAPPING[object] - end - - # Get the value as it was stored in the database, and convert to - # application-scope value. Converts invalid values to nil. - def demongoize(object) - INVERSE_MAPPING[object] - end - - # Converts the object that was supplied to a criteria and converts it - # into a query-friendly form. Returns invalid values as is. - def evolve(object) - MAPPING.fetch(object, object) - end - end - end - - class Profile - include Mongoid::Document - field :color, type: ColorMapping - end - - profile = Profile.new(color: 'white') - profile.color - # => "white" - - # Writes 0 to color field - profile.save! - - -.. _custom-field-options: - -Custom Field Options --------------------- - -You may define custom options for the ``field`` macro function -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, -specifying its handler function as a block: - -.. code-block:: ruby - - # in /config/initializers/mongoid_custom_fields.rb - - Mongoid::Fields.option :max_length do |model, field, value| - model.validates_length_of field.name, maximum: value - end - -Then, use it your model class: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - field :name, type: String, max_length: 10 - end - -Note that the handler function will be invoked whenever the option is used -in the field definition, even if the option's value is false or nil. - -.. _mongoid-dynamic-fields: - -Dynamic Fields -============== - -By default, Mongoid requires all fields that may be set on a document to -be explicitly defined using ``field`` declarations. Mongoid also supports -creating fields on the fly from an arbitrary hash or documents stored in -the database. When a model uses fields not explicitly defined, such fields -are called *dynamic fields*. - -To enable dynamic fields, include ``Mongoid::Attributes::Dynamic`` module -in the model: - -.. code-block:: ruby - - class Person - include Mongoid::Document - include Mongoid::Attributes::Dynamic - end - - bob = Person.new(name: 'Bob', age: 42) - bob.name - # => "Bob" - -It is possible to use ``field`` declarations and dynamic fields in the same -model class. Attributes for which there is a ``field`` declaration will be -treated according to the ``field`` declaration, with remaining attributes -being treated as dynamic fields. - -Attribute values in the dynamic fields must initially be set by either -passing the attribute hash to the constructor, mass assignment via -``attributes=``, mass assignment via ``[]=``, using ``write_attribute``, -or they must already be present in the database. - -.. code-block:: ruby - - # OK - bob = Person.new(name: 'Bob') - - # OK - bob = Person.new - bob.attributes = {age: 42} - - # OK - bob = Person.new - bob['age'] = 42 - - # Raises NoMethodError: undefined method age= - bob = Person.new - bob.age = 42 - - # OK - bob = Person.new - # OK - string access - bob.write_attribute('age', 42) - # OK - symbol access - bob.write_attribute(:name, 'Bob') - - # OK, initializes attributes from whatever is in the database - bob = Person.find('123') - -If an attribute is not present in a particular model instance's attributes -hash, both the reader and the writer for the corresponding field are not -defined, and invoking them raises ``NoMethodError``: - -.. code-block:: ruby - - bob = Person.new - bob.attributes = {age: 42} - - bob.age - # => 42 - - # raises NoMethodError - bob.name - - # raises NoMethodError - bob.name = 'Bob' - - # OK - bob['name'] = 'Bob' - - bob.name - # => "Bob" - -Attributes can always be read using mass attribute access or ``read_attribute`` -(this applies to models not using dynamic fields as well): - -.. code-block:: ruby - - bob = Person.new(age: 42) - - # OK - string access - bob['name'] - # => nil - - # OK - symbol access - bob[:name] - # => nil - - # OK - string access - bob['age'] - # => 42 - - # OK - symbol access - bob[:age] - # => 42 - - # OK - bob.attributes['name'] - # => nil - - # OK - bob.attributes['age'] - # => 42 - - # Returns nil - keys are always strings - bob.attributes[:age] - # => nil - - # OK - bob.read_attribute('name') - # => nil - - # OK - bob.read_attribute(:name) - # => nil - - # OK - string access - bob.read_attribute('age') - # => 42 - - # OK - symbol access - bob.read_attribute(:age) - # => 42 - -.. note:: - - The values returned from the ``read_attribute`` method, and those stored in - the ``attributes`` hash, are the ``mongoized`` values. - - -Special Characters in Field Names ---------------------------------- - -Mongoid permits dynamic field names to include spaces and punctuation: - -.. code-block:: ruby - - bob = Person.new('hello world' => 'MDB') - bob.send('hello world') - # => "MDB" - - bob.write_attribute("hello%world", 'MDB') - bob[:"hello%world"] - # => "MDB" - - -.. _localized-fields: - -Localized Fields -================ - -Mongoid supports localized fields via the `I18n gem `_. - -.. code-block:: ruby - - class Product - include Mongoid::Document - field :description, type: String, localize: true - end - -By telling the field to ``localize``, Mongoid will under the covers store the field -as a hash of locale/value pairs, but normal access to it will behave like a string. - -.. code-block:: ruby - - I18n.default_locale = :en - product = Product.new - product.description = "Marvelous!" - I18n.locale = :de - product.description = "Fantastisch!" - - product.attributes - # { "description" => { "en" => "Marvelous!", "de" => "Fantastisch!" } - -You can get and set all the translations at once by using the corresponding ``_translations`` method. - -.. code-block:: ruby - - product.description_translations - # { "en" => "Marvelous!", "de" => "Fantastisch!" } - product.description_translations = - { "en" => "Marvelous!", "de" => "Wunderbar!" } - -Localized fields can be used with any field type. For example, they can be used -with float fields for differences with currency: - -.. code:: ruby - - class Product - include Mongoid::Document - - field :price, type: Float, localize: true - field :currency, type: String, localize: true - end - -By creating the model in this way, we can separate the price from the currency -type, which allows you to use all of the number-related functionalities on the -price when querying or aggregating that field (provided that you index into the -stored translations hash). We can create an instance of this model as follows: - -.. code:: ruby - - product = Product.new - I18n.locale = :en - product.price = 1.00 - product.currency = "$" - I18n.locale = :he - product.price = 3.24 - product.currency = "₪" - - product.attributes - # => { "price" => { "en" => 1.0, "he" => 3.24 }, "currency" => { "en" => "$", "he" => "₪" } } - - -.. _present-fields: - -Localize ``:present`` Field Option ----------------------------------- - -Mongoid supports the ``:present`` option when creating a localized field: - -.. code-block:: ruby - - class Product - include Mongoid::Document - field :description, localize: :present - end - -This option automatically removes ``blank`` values (i.e. those that return true -for the ``blank?`` method) from the ``_translations`` hash: - -.. code-block:: ruby - - I18n.default_locale = :en - product = Product.new - product.description = "Marvelous!" - I18n.locale = :de - product.description = "Fantastisch!" - - product.description_translations - # { "en" => "Marvelous!", "de" => "Fantastisch!" } - - product.description = "" - product.description_translations - # { "en" => "Marvelous!" } - -When the empty string is written for the ``:de`` locale, the ``"de"`` key is -removed from the ``_translations`` hash instead of writing the empty string. - - -Fallbacks ---------- - -Mongoid integrates with -`i18n fallbacks `_. -To use the fallbacks, the respective functionality must be explicitly enabled. - -In a Rails application, set the ``config.i18n.fallbacks`` configuration setting -to ``true`` in your environment and specify the fallback languages: - -.. code-block:: ruby - - config.i18n.fallbacks = true - config.after_initialize do - I18n.fallbacks[:de] = [ :en, :es ] - end - -In a non-Rails application, include the fallbacks module into the I18n backend -you are using and specify the fallback languages: - -.. code-block:: ruby - - require "i18n/backend/fallbacks" - I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) - I18n.fallbacks[:de] = [ :en, :es ] - -When fallbacks are enabled, if a translation is not present in the active -language, translations will be looked up in the fallback languages: - -.. code-block:: ruby - - product = Product.new - I18n.locale = :en - product.description = "Marvelous!" - I18n.locale = :de - product.description # "Marvelous!" - -Mongoid also defines a ``:fallbacks`` option on fields, which can be used to -disable fallback functionality on a specific field: - -.. code:: ruby - - class Product - include Mongoid::Document - field :description, type: String, localize: true, fallbacks: false - end - - product = Product.new - I18n.locale = :en - product.description = "Marvelous!" - I18n.locale = :de - product.description # nil - -Note that this option defaults to ``true``. - -.. note:: - - In i18n 1.1, the behavior of fallbacks `changed `_ - to always require an explicit list of fallback locales rather than falling - back to the default locale when no fallback locales have been provided. - - -Querying --------- - -When querying for localized fields using Mongoid's criteria API, Mongoid will automatically -alter the criteria to match the current locale. - -.. code-block:: ruby - - # Match all products with Marvelous as the description. Locale is en. - Product.where(description: "Marvelous!") - # The resulting MongoDB query filter: { "description.en" : "Marvelous!" } - - -Indexing --------- - -If you plan to be querying extensively on localized fields, you should index each of the -locales that you plan on searching on. - -.. code-block:: ruby - - class Product - include Mongoid::Document - field :description, localize: true - - index "description.de" => 1 - index "description.en" => 1 - end - - -.. _read-only: - -Read-Only Attributes -==================== - -You can tell Mongoid that certain attributes are read-only. This will allow -documents to be created with these attributes, but changes to them will be -ignored when using mass update methods such as ``update_attributes``: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :origin, type: String - - attr_readonly :name, :origin - end - - band = Band.create(name: "Placebo") - band.update_attributes(name: "Tool") # Filters out the name change. - -If you explicitly try to update or remove a read-only attribute by itself, -a ``ReadonlyAttribute`` exception will be raised: - -.. code-block:: ruby - - band.update_attribute(:name, "Tool") # Raises the error. - band.remove_attribute(:name) # Raises the error. - -Assignments to read-only attributes using their setters will be ignored: - -.. code-block:: ruby - - b = Band.create!(name: "The Rolling Stones") - # => # - b.name = "The Smashing Pumpkins" - # => "The Smashing Pumpkins" - b.name - # => "The Rolling Stones" - -Calls to atomic persistence operators, like ``bit`` and ``inc``, will persist -changes to read-only fields. - -Timestamp Fields -================ - -Mongoid supplies a timestamping module in ``Mongoid::Timestamps`` which -can be included to get basic behavior for ``created_at`` and -``updated_at`` fields. - -.. code-block:: ruby - - class Person - include Mongoid::Document - include Mongoid::Timestamps - end - -You may also choose to only have specific timestamps for creation or -modification. - -.. code-block:: ruby - - class Person - include Mongoid::Document - include Mongoid::Timestamps::Created - end - - class Post - include Mongoid::Document - include Mongoid::Timestamps::Updated - end - -If you want to turn off timestamping for specific calls, use the timeless -method: - -.. code-block:: ruby - - person.timeless.save - Person.timeless.create! - -If you'd like shorter timestamp fields with aliases on them to save space, -you can include the short versions of the modules. - -.. code-block:: ruby - - class Band - include Mongoid::Document - include Mongoid::Timestamps::Short # For c_at and u_at. - end - - class Band - include Mongoid::Document - include Mongoid::Timestamps::Created::Short # For c_at only. - end - - class Band - include Mongoid::Document - include Mongoid::Timestamps::Updated::Short # For u_at only. - end - - -.. _field-names-with-periods-and-dollar-signs: - -Field Names with Dots/Periods (``.``) and Dollar Signs (``$``) -============================================================== - -Using dots/periods (``.``) in fields names and starting a field name with -a dollar sign (``$``) is not recommended, as Mongoid provides limited support -for retrieving and operating on the documents stored in those fields. - -Both Mongoid and MongoDB query language (MQL) generally use the dot/period -character (``.``) to separate field names in a field path that traverses -embedded documents, and words beginning with the dollar sign (``$``) as -operators. MongoDB provides `limited support -`_ -for using field names containing dots and starting with the dollar sign -for interoperability with other software, -however, due to this support being confined to specific operators -(e.g. :manual:`getField `, -:manual:`setField `) and -requiring the usage of the aggregation pipeline for both queries and updates, -applications should avoid using dots in field names and starting field names -with the dollar sign if possible. - -Mongoid, starting in version 8, now allows users to access fields that begin with -dollar signs and that contain dots/periods. They can be accessed using the ``send`` -method as follows: - -.. code:: - - class User - include Mongoid::Document - field :"first.last", type: String - field :"$_amount", type: Integer - end - - user = User.first - user.send(:"first.last") - # => Mike.Trout - user.send(:"$_amount") - # => 42650000 - -It is also possible to use ``read_attribute`` to access these fields: - -.. code:: - - user.read_attribute("first.last") - # => Mike.Trout - -Due to `server limitations `_, -updating and replacing fields containing dots and dollars requires using special -operators. For this reason, calling setters on these fields is prohibited and -will raise an error: - -.. code:: - - class User - include Mongoid::Document - field :"first.last", type: String - field :"$_amount", type: Integer - end - - user = User.new - user.send(:"first.last=", "Shohei.Ohtani") - # raises a InvalidDotDollarAssignment error - user.send(:"$_amount=", 8500000) - # raises a InvalidDotDollarAssignment error - diff --git a/source/reference/indexes.txt b/source/reference/indexes.txt deleted file mode 100644 index f5de6805..00000000 --- a/source/reference/indexes.txt +++ /dev/null @@ -1,279 +0,0 @@ -.. _mongoid-indexes: -.. _indexes: - -**************** -Index Management -**************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Specifying Indexes -================== - -You can define indexes on documents using the index macro. Provide the key for -the index along with a direction. Additional options can be supplied in the -second options hash parameter: - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :ssn - - index({ ssn: 1 }, { unique: true, name: "ssn_index" }) - end - -You can define indexes on embedded document fields as well: - -.. code-block:: ruby - - class Person - include Mongoid::Document - embeds_many :addresses - index "addresses.street" => 1 - end - -You can index on multiple fields and provide direction: - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :first_name - field :last_name - - index({ first_name: 1, last_name: 1 }, { unique: true }) - end - -Indexes can be sparse: - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :ssn - - index({ ssn: -1 }, { sparse: true }) - end - -For geospatial indexes, make sure the field being indexed is of type Array: - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :location, type: Array - - index({ location: "2d" }, { min: -200, max: 200 }) - end - -Indexes can be scoped to a specific database: - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :ssn - index({ ssn: 1 }, { database: "users", unique: true, background: true }) - end - -You may use aliased field names in index definitions. Field aliases -will also be resolved on the following options: ``partial_filter_expression``, -``weights``, ``wildcard_projection``. - -.. code-block:: ruby - - 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. - -Mongoid can define indexes on "foreign key" fields for associations. -This only works on the association macro that the foreign key is stored on: - -.. code-block:: ruby - - class Comment - include Mongoid::Document - belongs_to :post, index: true - has_and_belongs_to_many :preferences, index: true - end - -*Deprecated:* In MongoDB 4.0 and earlier, users could control whether to build indexes -in the foreground (blocking) or background (non-blocking, but less efficient) using the -``background`` option. - -.. code-block:: ruby - - class Person - include Mongoid::Document - field :ssn - index({ ssn: 1 }, { unique: true, background: true }) - end - -The default value of ``background`` is controlled by Mongoid's -``background_indexing`` :ref:`configuration option `. - -The ``background`` option has `no effect as of MongoDB 4.2 -`_. - - -Specifying Search Indexes on MongoDB Atlas -========================================== - -If your application is connected to MongoDB Atlas, you can declare and manage -search indexes on your models. (This feature is only available on MongoDB -Atlas.) - -To declare a search index, use the ``search_index`` macro in your model: - -.. code-block:: ruby - - class Message - include Mongoid::Document - - search_index { ... } - search_index :named_index, { ... } - end - -Search indexes may be given an explicit name; this is necessary if you have -more than one search index on a model. - - -Index Management Rake Tasks -=========================== - -When you want to create the indexes in the database, use the provided -``db:mongoid:create_indexes`` Rake task: - -.. code-block:: bash - - $ rake db:mongoid:create_indexes - -Mongoid also provides a Rake task to delete all secondary indexes. - -.. code-block:: bash - - $ rake db:mongoid:remove_indexes - -Note: the output of these Rake tasks goes to the default logger configured -by Rails. This is usually a file like ``log/development.log`` and not standard -output. - -These create/remove indexes commands also works for just one model by running -in Rails console: - -.. code-block:: ruby - - # Create indexes for Model - Model.create_indexes - - # Remove indexes for Model - Model.remove_indexes - -Managing Search Indexes on MongoDB Atlas ----------------------------------------- - -If you have defined search indexes on your model, there are rake tasks available -for creating and removing those search indexes: - -.. code-block:: bash - - $ rake db:mongoid:create_search_indexes - $ rake db:mongoid:remove_search_indexes - -By default, creating search indexes will wait for the indexes to be created, -which can take quite some time. If you want to simply let the database create -the indexes in the background, you can set the ``WAIT_FOR_SEARCH_INDEXES`` -environment variable to 0, like this: - -.. code-block:: bash - - $ rake WAIT_FOR_SEARCH_INDEXES=0 db:mongoid:create_search_indexes - -Note that the task for removing search indexes will remove all search indexes -from all models, and should be used with caution. - -You can also add and remove search indexes for a single model by invoking the -following in a Rails console: - -.. code-block:: ruby - - # Create all defined search indexes on the model; this will return - # immediately and the indexes will be created in the background. - Model.create_search_indexes - - # Remove all search indexes from the model - Model.remove_search_indexes - - # Enumerate all search indexes on the model - Model.search_indexes.each { |index| ... } - - -Telling Mongoid Where to Look For Models ----------------------------------------- - -For non-Rails applications, Mongoid's rake tasks will look for models in -``./app/models`` and ``./lib/models``. For Rails, Mongoid will look in -``./app/models`` (or wherever you've configured Rails to look for models). If -your models are in another location, you will need to tell Mongoid where to -look for them with ``Mongoid.model_paths=``. You can do this by setting it -in your application's Rakefile: - -.. code-block:: ruby - - # Rakefile - - # keep the defaults, but add more paths to look for models - Mongoid.model_paths += [ "./src/models", "./lib/documents" ] - - # or, override the defaults entirely - Mongoid.model_paths = [ "./src/models", "./lib/documents" ] - -Make sure that these paths are in your application's load path, as well. For -example: - -.. code-block:: ruby - - # Rakefile - - $LOAD_PATHS.concat [ "./src/models", "./lib/documents" ] - - -Using Rake Tasks With Non-Rails Applications --------------------------------------------- - -Mongoid's Rake tasks are automatically loaded in Rails applications using -Mongoid. When using Mongoid with a non-Rails application, these tasks must -be loaded manually: - -.. code-block:: ruby - - # Rakefile - - require 'mongoid' - load 'mongoid/tasks/database.rake' - -If your application uses Bundler, you can require ``bundler/setup`` instead of -explicitly requiring ``mongoid``: - -.. code-block:: ruby - - # Rakefile - - require 'bundler/setup' - load 'mongoid/tasks/database.rake' \ No newline at end of file diff --git a/source/reference/inheritance.txt b/source/reference/inheritance.txt deleted file mode 100644 index dcba02a9..00000000 --- a/source/reference/inheritance.txt +++ /dev/null @@ -1,308 +0,0 @@ -.. _inheritance: - -*********** -Inheritance -*********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -.. _inheritance-overview: - -Overview -======== - -Mongoid supports inheritance in both top level and embedded documents. When -a child document inherits from a parent document, the parent document's -fields, associations, validations and scopes are copied to the child document. - -.. code-block:: ruby - - class Canvas - include Mongoid::Document - field :name, type: String - embeds_many :shapes - end - - class Browser < Canvas - field :version, type: Integer - scope :recent, ->{ where(:version.gt => 3) } - end - - class Firefox < Browser - end - - class Shape - include Mongoid::Document - field :x, type: Integer - field :y, type: Integer - embedded_in :canvas - end - - class Circle < Shape - field :radius, type: Float - end - - class Rectangle < Shape - field :width, type: Float - field :height, type: Float - end - -In the above example, ``Canvas``, ``Browser`` and ``Firefox`` will all save in the canvases -collection. An additional attribute ``_type`` is stored in order to make sure when loaded -from the database the correct document is returned. This also holds true for the embedded -documents ``Circle``, ``Rectangle``, and ``Shape``. - -.. note:: - - When searching for a ``Circle``, the query will only return documents in the shape collection - where the ``_type`` (or whatever the discriminator key was set to) field has the value ``Circle`` (or - whatever the discriminator value was set to), all other discriminator values will be considered an object - of the Shape class. - - Similarly, when querying by parent classes (``Canvas`` in this example), any documents in the collection - that do not have a discriminator value, or whose discriminator value does not map to either the parent - or any of its descendants, will be returned as instances of the parent class. - - -.. _discriminator-key: - -Changing the Discriminator Key -============================== - -Mongoid supports changing the discriminator key from the default ``_type``. There are a few -cases where one might want to do this: - -1. For optimization: The user might want to use a shorter key like ``_t``. - -2. When trying to work with an existing system: It's possible the user is working with an existing system or dataset that has predefined keys. - - -There are two ways to change the discriminator key, on the class level and on the global level. -To change the discriminator key on the class level the user can set it directly on the parent class using -the ``discriminator_key=`` method. -Take the above example: - -.. code-block:: ruby - - class Shape - include Mongoid::Document - field :x, type: Integer - field :y, type: Integer - embedded_in :canvas - - self.discriminator_key = "shape_type" - end - - class Circle < Shape - field :radius, type: Float - end - - class Rectangle < Shape - field :width, type: Float - field :height, type: Float - end - -Here a call to the ``discriminator_key=`` setter was added to the parent class. Now, on -creation of a Rectangle or Circle, a ``shape_type`` field will be added. - -Note that the discriminator key can only be modified in the parent class, and an error -will be raised if trying to set it on the child class. - -If the discriminator key is changed after the child class is created, a new field is -added with the new discriminator key value, and the old field will remain unchanged. -For example: - -.. code-block:: ruby - - class Shape - include Mongoid::Document - field :x, type: Integer - field :y, type: Integer - embedded_in :canvas - end - - class Circle < Shape - field :radius, type: Float - end - - class Rectangle < Shape - field :width, type: Float - field :height, type: Float - end - - Shape.discriminator_key = "shape_type" - -In this case, on creation of a Rectangle or Circle, there will be both a ``shape_type`` -and a ``_type`` field that both default to ``Rectangle`` or ``Circle`` respectively. - - -The discriminator key can also be set on the global level. Meaning, all classes will -use the globally set discriminator key instead of ``_type``. Take the above example: - -.. code-block:: ruby - - Mongoid.discriminator_key = "_the_type" - - class Shape - include Mongoid::Document - field :x, type: Integer - field :y, type: Integer - embedded_in :canvas - end - - class Circle < Shape - field :radius, type: Float - end - - class Rectangle < Shape - field :width, type: Float - field :height, type: Float - end - -After setting the global discriminator key, all classes will use ``_the_type`` as -the discriminator key and will not contain a ``_type`` field. - -Note that when defining the discriminator key on the global level, it must be set before the -child class is defined for the child class to use that global value. -On the global level, however, if the user does not set the discriminator key before defining a child -class, the discriminator field will use the default ``_type`` and not the new global setting in -that child class. - - -.. _discriminator-value: - -Changing the Discriminator Value -================================ - -Mongoid also supports changing the discriminator value from the default value, which is the class name. -One can change the discriminator value by using the ``discriminator_value=`` method on that specific class. - -Take the above example: - -.. code-block:: ruby - - class Shape - include Mongoid::Document - field :x, type: Integer - field :y, type: Integer - embedded_in :canvas - end - - class Circle < Shape - field :radius, type: Float - - self.discriminator_value = "round thing" - end - - class Rectangle < Shape - field :width, type: Float - field :height, type: Float - end - -Here, a call to the ``discriminator_value=`` setter was added to ``Circle``. -Now, on creation of a ``Circle``, the document will contain a field with the key ``_type`` (or whatever the ``discriminator_key`` was changed to) -and the value "round thing." - -.. note:: - - Because the discriminator value overrides are declared in child classes, - the child classes potentially found by a query must be loaded prior to - sending that query. In the above example, the ``Circle`` class definition - must be loaded when querying on ``Shape`` if the returned documents could - potentially be instances of ``Circle`` (since autoloading wouldn't resolve - ``"round thing"`` to ``Circle``). - - -Querying Subclasses -=================== - -Querying for subclasses is handled in the normal manner, and although the documents are -all in the same collection, queries will only return documents of the correct type, -similar to Single Table Inheritance in ActiveRecord. - -.. code-block:: ruby - - # Returns Canvas documents and subclasses - Canvas.where(name: "Paper") - # Returns only Firefox documents - Firefox.where(name: "Window 1") - - -Associations -============ - -You can add any type of subclass to a has one or has many association, through -either normal setting or through the build and create methods on the association: - -.. code-block:: ruby - - firefox = Firefox.new - # Builds a Shape object - firefox.shapes.build({ x: 0, y: 0 }) - # Builds a Circle object - firefox.shapes.build({ x: 0, y: 0 }, Circle) - # Creates a Rectangle object - firefox.shapes.create({ x: 0, y: 0 }, Rectangle) - - rect = Rectangle.new(width: 100, height: 200) - firefox.shapes - - -.. _inheritance-persistence-context: - -Persistence Contexts -==================== - -Mongoid allows the persistence context of a subclass to be changed from the -persistence context of its parent. This means that, using the ``store_in`` -method, we can store the documents of the subclasses in different collections -(as well as different databases, clients) than their parents: - -.. code:: ruby - - class Shape - include Mongoid::Document - store_in collection: :shapes - end - - class Circle < Shape - store_in collection: :circles - end - - class Square < Shape - store_in collection: :squares - end - - Shape.create! - Circle.create! - Square.create! - -Setting the collection on the children causes the documents for those children -to be stored in the set collection, instead of in the parent's collection: - -.. code:: javascript - - > db.shapes.find() - { "_id" : ObjectId("62fe9a493282a43d6b725e10"), "_type" : "Shape" } - > db.circles.find() - { "_id" : ObjectId("62fe9a493282a43d6b725e11"), "_type" : "Circle" } - > db.squares.find() - { "_id" : ObjectId("62fe9a493282a43d6b725e12"), "_type" : "Square" } - -If the collection is set on some of the subclasses and not others, the subclasses -with set collections will store documents in those collections, and the -subclasses without set collections will be store documents in the parent's -collection. - -.. note:: - - Note that changing the collection that a subclass is stored in will cause - documents of that subclass to no longer be found in the results of querying - its parent class. diff --git a/source/reference/nested-attributes.txt b/source/reference/nested-attributes.txt deleted file mode 100644 index 5803206e..00000000 --- a/source/reference/nested-attributes.txt +++ /dev/null @@ -1,140 +0,0 @@ -.. _nested-attributes: - -***************** -Nested Attributes -***************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Nested attributes provide a mechanism for updating documents and their -associations in a single operation by nesting attributes in a single -parameters hash. This is useful when wanting to edit multiple documents -within a single web form. - -Behavior -======== - -Nested attributes can be enabled for any association, embedded or referenced. -To enable this for an association, simply provide the association name to the -``accepts_nested_attributes_for`` macro. - -.. code-block:: ruby - - class Band - include Mongoid::Document - embeds_many :albums - belongs_to :producer - accepts_nested_attributes_for :albums, :producer - end - -Note that when you add nested attributes functionality to a referenced -association, Mongoid will automatically enable autosave for that association. - -When an association gains nested attributes behavior, an additional method is -added to the base model, which should be used to update the attributes with -the new functionality. This method is the association name plus ``_attributes=``. -You can use this method directly, or more commonly the name of the method can -be an attribute in the updates for the base class, in which case -Mongoid will call the appropriate setter under the covers. - -.. code-block:: ruby - - band = Band.first - band.producer_attributes = { name: "Flood" } - band.attributes = { producer_attributes: { name: "Flood" }} - -Note that this will work with any attribute based setter method in Mongoid, -including ``update``, ``update_attributes`` and ``attributes=``, as well as -``create`` (and all of their corresponding bang methods). For example, creating -a new person with associated address records can be done in a single -statement, like this: - -.. code-block:: ruby - - person = Person.create( - name: 'John Schmidt', - addresses_attributes: [ - { type: 'home', street: '1234 Street Ave.', city: 'Somewhere' }, - { type: 'work', street: 'Parkway Blvd.', city: 'Elsewehre' }, - ]) - - -Creating Records ----------------- - -You can create new nested records via nested attributes by omitting -an ``_id`` field: - -.. code-block:: ruby - - person = Person.first - person.update(addresses_attributes: [ - { type: 'prior', street: '221B Baker St', city: 'London' } ]) - -This will append the new record to the existing set; existing records will -not be changed. - - -Updating Records ----------------- - -If you specify an ``_id`` field for any of the nested records, the attributes -will be used to update the record with that id: - -.. code-block:: ruby - - person = Person.first - address = person.addresses.first - person.update(addresses_attributes: [ - { _id: address._id, city: 'Lisbon' } ]) - -Note that if there is no record with that id, a ``Mongoid::Errors::DocumentNotFound`` -exception will be raised. - - -Destroying Records ------------------- - -You can also destroy records this way, by specifying a special -``_destroy`` attribute. In order to use this, you must have passed -``allow_destroy: true`` with the ``accepts_nested_attributes_for`` -declaration: - -.. code-block:: ruby - - class Person - # ... - - accepts_nested_attributes_for :addresses, allow_destroy: true - end - - person = Person.first - address = person.addresses.first - person.update(addresses_attributes: [ - { _id: address._id, _destroy: true } ]) - -Note that, as with updates, if there is no record with that id, -a ``Mongoid::Errors::DocumentNotFound`` exception will be raised. - - -Combining Operations --------------------- - -Nested attributes allow you to combine all of these operations in -a single statement! Here's an example that creates an address, -updates another address, and destroys yet another address, all in -a single command: - -.. code-block:: ruby - - person = Person.first - person.update(addresses_attributes: [ - { type: 'alt', street: '1234 Somewhere St.', city: 'Cititon' }, - { _id: an_address_id, city: 'Changed City' }, - { _id: another_id, _destroy: true } ]) diff --git a/source/reference/queries.txt b/source/reference/queries.txt deleted file mode 100644 index 0ef52259..00000000 --- a/source/reference/queries.txt +++ /dev/null @@ -1,2480 +0,0 @@ -.. _queries: - -******* -Queries -******* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -Mongoid provides a rich query DSL inspired by ActiveRecord. A trivial query -looks as follows: - -.. code-block:: ruby - - Band.where(name: "Depeche Mode") - -A more complex query utilizing various Mongoid features could be as follows: - -.. code-block:: ruby - - Band. - where(:founded.gte => "1980-01-01"). - in(name: [ "Tool", "Deftones" ]). - union. - in(name: [ "Melvins" ]) - -The query methods return ``Mongoid::Criteria`` objects, which are chainable -and lazily evaluated wrappers for MongoDB query language (MQL). -The queries are executed when their result sets are iterated. For example: - -.. code-block:: ruby - - # Construct a Criteria object: - - Band.where(name: 'Deftones') - # => #"Deftones"} - # options: {} - # class: Band - # embedded: false> - - # Evaluate the query and get matching documents: - - Band.where(name: 'Deftones').to_a - # => [#] - -Methods like ``first`` and ``last`` return the individual documents immediately. -Otherwise, iterating a Criteria object with methods like ``each`` or ``map`` -retrieves the documents from the server. ``to_a`` can be used to force -execution of a query that returns an array of documents, literally converting -a Criteria object to an Array. - -When a query method is called on a Criteria instance, the method returns a new -Criteria instance with the new conditions added to the existing conditions: - -.. code-block:: ruby - - scope = Band.where(:founded.gte => "1980-01-01") - # => #{"$gte"=>"1980-01-01"}} - # options: {} - # class: Band - # embedded: false> - - scope.where(:founded.lte => "2020-01-01") - # => #{"$gte"=>"1980-01-01", "$lte"=>"2020-01-01"}} - # options: {} - # class: Band - # embedded: false> - - scope - # => #{"$gte"=>"1980-01-01"}} - # options: {} - # class: Band - # embedded: false> - - -Condition Syntax -================ - -Mongoid supports three ways of specifying individual conditions: - -1. Field syntax. -2. MQL syntax. -3. Symbol operator syntax. - -All syntaxes support querying embedded documents using the dot notation. -All syntaxes respect field types, if the field being queried is defined in the -model class, and field aliases. - -The examples in this section use the following model definition: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, type: String - field :founded, type: Integer - field :m, as: :member_count, type: Integer - - embeds_one :manager - end - - class Manager - include Mongoid::Document - - embedded_in :band - - field :name, type: String - end - -Field Syntax ------------- - -The simplest querying syntax utilizes the basic Ruby hashes. Keys can be -symbols or strings, and correspond to field names in MongoDB documents: - -.. code-block:: ruby - - Band.where(name: "Depeche Mode") - # => #"Depeche Mode"} - # options: {} - # class: Band - # embedded: false> - - # Equivalent to: - - Band.where("name" => "Depeche Mode") - -MQL Syntax ----------- - -An MQL operator may be specified on any field using the hash syntax: - -.. code-block:: ruby - - Band.where(founded: {'$gt' => 1980}) - # => #{"$gt"=>1980}} - # options: {} - # class: Band - # embedded: false> - - # Equivalent to: - - Band.where('founded' => {'$gt' => 1980}) - -Symbol Operator Syntax ----------------------- - -MQL operators may be specified as methods on symbols for the respective field -name, as follows: - -.. code-block:: ruby - - Band.where(:founded.gt => 1980) - # => #{"$gt"=>1980}} - # options: {} - # class: Band - # embedded: false> - - -Fields -====== - -Querying on Defined Fields --------------------------- - -In order to query on a field, it is not necessary to add the field to -:ref:`the model class definition `. However, if a field is defined in -the model class, Mongoid will coerce query values to match defined field types -when constructing the query: - -.. code-block:: ruby - - Band.where(name: 2020, founded: "2020") - # => #"2020", "founded"=>2020} - # options: {} - # class: Band - # embedded: false> - -Querying for Raw Values ------------------------ - -If you'd like to bypass Mongoid's query type coercion behavior and query -directly for the raw-typed value in the database, wrap the query value in -``Mongoid::RawValue`` class. This can be useful when working with legacy data. - -.. code-block:: ruby - - Band.where(founded: Mongoid::RawValue("2020")) - # => #"2020"} - # options: {} - # class: Band - # embedded: false> - -Field Aliases -------------- - -Queries take into account :ref:`storage field names ` -and :ref:`field aliases `: - -.. code-block:: ruby - - Band.where(name: 'Astral Projection') - # => #"Astral Projection"} - # options: {} - # class: Band - # embedded: false> - -Since ``id`` and ``_id`` fields are aliases, either one can be used for queries: - -.. code-block:: ruby - - Band.where(id: '5ebdeddfe1b83265a376a760') - # => #BSON::ObjectId('5ebdeddfe1b83265a376a760')} - # options: {} - # class: Band - # embedded: false> - - -Embedded Documents -================== - -To match values of fields of embedded documents, use the dot notation: - -.. code-block:: ruby - - Band.where('manager.name' => 'Smith') - # => #"Smith"} - # options: {} - # class: Band - # embedded: false> - - Band.where(:'manager.name'.ne => 'Smith') - # => #{"$ne"=>"Smith"}} - # options: {} - # class: Band - # embedded: false> - -.. note:: - - Queries always return top-level model instances, even if all of the - conditions are referencing embedded documents. - - -.. _logical-operations: - -Logical Operations -================== - -Mongoid supports ``and``, ``or``, ``nor`` and ``not`` logical operations on -``Criteria`` objects. These methods take one or more hash of conditions -or another ``Criteria`` object as their arguments, with ``not`` additionally -having an argument-free version. - -.. code-block:: ruby - - # and with conditions - Band.where(label: 'Trust in Trance').and(name: 'Astral Projection') - - # or with scope - Band.where(label: 'Trust in Trance').or(Band.where(name: 'Astral Projection')) - - # not with conditions - Band.not(label: 'Trust in Trance', name: 'Astral Projection') - - # argument-less not - Band.not.where(label: 'Trust in Trance', name: 'Astral Projection') - -For backwards compatibility with earlier Mongoid versions, all of the logical -operation methods also accept arrays of parameters, which will be flattened -to obtain the criteria. Passing arrays to logical operations is deprecated and -may be removed in a future version of Mongoid. - -The following calls all produce the same query conditions: - -.. code-block:: ruby - - # Condition hashes passed to separate and invocations - Band.and(name: 'SUN Project').and(member_count: 2) - - # Multiple condition hashes in the same and invocation - Band.and({name: 'SUN Project'}, {member_count: 2}) - - # Multiple condition hashes in an array - deprecated - Band.and([{name: 'SUN Project'}, {member_count: 2}]) - - # Condition hash in where and a scope - Band.where(name: 'SUN Project').and(Band.where(member_count: 2)) - - # Condition hash in and and a scope - Band.and({name: 'SUN Project'}, Band.where(member_count: 2)) - - # Scope as an array element, nested arrays - deprecated - Band.and([Band.where(name: 'SUN Project'), [{member_count: 2}]]) - - # All produce: - # => #"SUN Project", "member_count"=>2} - # options: {} - # class: Band - # embedded: false> - - -Operator Combinations ---------------------- - -As of Mongoid 7.1, logical operators (``and``, ``or``, ``nor`` and ``not``) -have been changed to have the the same semantics as `those of ActiveRecord -`_. -To obtain the semantics of ``or`` as it behaved in Mongoid 7.0 and earlier, -use ``any_of`` which is described below. - -When conditions are specified on the same field multiple times, all -conditions are added to the criteria: - -.. code-block:: ruby - - Band.where(name: 1).where(name: 2).selector - # => {"name"=>"1", "$and"=>[{"name"=>"2"}]} - - Band.where(name: 1).or(name: 2).selector - # => {"$or"=>[{"name"=>"1"}, {"name"=>"2"}]} - -``any_of``, ``none_of``, ``nor`` and ``not`` behave similarly, with ``not`` producing -different query shapes as described below. - -When ``and``, ``or`` and ``nor`` logical operators are used, they -operate on the criteria built up to that point and its argument. -``where`` has the same meaning as ``and``: - -.. code-block:: ruby - - # or joins the two conditions - Band.where(name: 'Sun').or(label: 'Trust').selector - # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}]} - - # or applies only to the first condition, the second condition is added - # to the top level as $and - Band.or(name: 'Sun').where(label: 'Trust').selector - # => {"$or"=>[{"name"=>"Sun"}], "label"=>"Trust"} - - # Same as previous example - where and and are aliases - Band.or(name: 'Sun').and(label: 'Trust').selector - # => {"$or"=>[{"name"=>"Sun"}], "label"=>"Trust"} - - # Same operator can be stacked any number of times - Band.or(name: 'Sun').or(label: 'Trust').selector - # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}]} - - # The label: Foo condition is added to the top level as $and - Band.where(name: 'Sun').or(label: 'Trust').where(label: 'Foo').selector - # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}], "label"=>"Foo"} - - -``and`` Behavior ----------------- - -The ``and`` method will add new simple conditions to the top level of the -criteria, unless the receiving criteria already has a condition on the -respective fields, in which case the conditions will be combined with ``$and``. - -.. code-block:: ruby - - Band.where(label: 'Trust in Trance').and(name: 'Astral Projection').selector - # => {"label"=>"Trust in Trance Records", "name"=>"Astral Projection"} - - Band.where(name: /Best/).and(name: 'Astral Projection').selector - # => {"name"=>/Best/, "$and"=>[{"name"=>"Astral Projection"}]} - -As of Mongoid 7.1, specifying multiple criteria on the same field with ``and`` -combines all criteria so specified, whereas in previous versions of Mongoid -conditions on a field sometimes replaced previously specified conditions on -the same field, depending on which form of ``and`` was used. - - -``or``/``nor`` Behavior -_---------------------- - -``or`` and ``nor`` produce ``$or`` and ``$nor`` MongoDB operators, respectively, -using the receiver and all of the arguments as operands. For example: - -.. code-block:: ruby - - Band.where(name: /Best/).or(name: 'Astral Projection') - # => {"$or"=>[{"name"=>/Best/}, {"name"=>"Astral Projection"}]} - - Band.where(name: /Best/).and(name: 'Astral Projection'). - or(Band.where(label: /Records/)).and(label: 'Trust').selector - # => {"$or"=>[{"name"=>/Best/, "$and"=>[{"name"=>"Astral Projection"}]}, {"label"=>/Records/}], "label"=>"Trust"} - -If the only condition on the receiver is another ``or``/``nor``, the new -conditions are added to the existing list: - -.. code-block:: ruby - - Band.where(name: /Best/).or(name: 'Astral Projection'). - or(Band.where(label: /Records/)).selector - # => {"$or"=>[{"name"=>/Best/}, {"name"=>"Astral Projection"}, {"label"=>/Records/}]} - -Use ``any_of`` to add a disjunction to a Criteria object while maintaining -all of the conditions built up so far as they are. - - -.. _any-of: - -``any_of`` Behavior -------------------- - -``any_of`` adds a disjunction built from its arguments to the existing -conditions in the criteria. For example: - -.. code-block:: ruby - - Band.where(label: /Trust/).any_of({name: 'Astral Projection'}, {name: /Best/}) - # => {"label"=>/Trust/, "$or"=>[{"name"=>"Astral Projection"}, {"name"=>/Best/}]} - -The conditions are hoisted to the top level if possible: - -.. code-block:: ruby - - Band.where(label: /Trust/).any_of({name: 'Astral Projection'}) - # => {"label"=>/Trust/, "name"=>"Astral Projection"} - - -.. _none-of: - -``none_of`` Behavior --------------------- - -``none_of`` adds a negated disjunction ("nor") built from its arguments to -the existing conditions in the criteria. For example: - -.. code-block:: ruby - - Band.where(label: /Trust/).none_of({name: 'Astral Projection'}, {name: /Best/}) - # => {"label"=>/Trust/, "$nor"=>[{"name"=>"Astral Projection"}, {"name"=>/Best/}]} - - -``not`` Behavior ----------------- - -``not`` method can be called without arguments, in which case it will negate -the next condition that is specified. ``not`` can also be called with one -or more hash conditions or ``Criteria`` objects, which will all be negated and -added to the criteria. - -.. code-block:: ruby - - # not negates subsequent where - Band.not.where(name: 'Best').selector - # => {"name"=>{"$ne"=>"Best"}} - - # The second where is added as $and - Band.not.where(name: 'Best').where(label: /Records/).selector - # => {"name"=>{"$ne"=>"Best"}, "label"=>/Records/} - - # not negates its argument - Band.not(name: 'Best').selector - # => {"name"=>{"$ne"=>"Best"}} - -.. note:: - - ``$not`` in MongoDB server cannot be used with a string argument. - Mongoid uses ``$ne`` operator to achieve such a negation: - - .. code-block:: ruby - - # String negation - uses $ne - Band.not.where(name: 'Best').selector - # => {"name"=>{"$ne"=>"Best"}} - - # Regexp negation - uses $not - Band.not.where(name: /Best/).selector - # => {"name"=>{"$not"=>/Best/}} - -Similarly to ``and``, ``not`` will negate individual conditions for simple -field criteria. For complex conditions and when a field already has a condition -defined on it, since MongoDB server only supports the ``$not`` operator on -a per-field basis rather than globally, Mongoid emulates ``$not`` by using -an ``{'$and' => [{'$nor' => ...}]}`` construct: - -.. code-block:: ruby - - # Simple condition - Band.not(name: /Best/).selector - # => {"name"=>{"$not"=>/Best/}} - - # Complex conditions - Band.where(name: /Best/).not(name: 'Astral Projection').selector - # => {"name"=>/Best/, "$and"=>[{"$nor"=>[{"name"=>"Astral Projection"}]}]} - - # Symbol operator syntax - Band.not(:name.ne => 'Astral Projection') - # => #[{"$nor"=>[{"name"=>{"$ne"=>"Astral Projection"}}]}]} - # options: {} - # class: Band - # embedded: false> - -If using ``not`` with arrays or regular expressions, please note the -caveats/limitations of ``$not`` `stated in the MongoDB server documentation -`_. - - -Incremental Query Construction -============================== - -By default, when conditions are added to a query, Mongoid considers each -condition complete and independent from any other conditions potentially -present in the query. For example, calling ``in`` twice adds two separate -``$in`` conditions: - -.. code-block:: ruby - - Band.in(name: ['a']).in(name: ['b']) - => #{"$in"=>["a"]}, "$and"=>[{"name"=>{"$in"=>["b"]}}]} - options: {} - class: Band - embedded: false> - -Some operator methods support building the condition incrementally. In this -case, when an condition on a field which uses one of the supported operators -is being added, if there already is a condition on the same field using the -same operator, the operator expressions are combined according to the -specified *merge strategy*. - -.. _merge-strategies: - -Merge Strategies ----------------- - -Mongoid provides three merge strategies: - -- **Override**: the new operator instance replaces any existing conditions on - the same field using the same operator. -- **Intersect**: if there already is a condition using the same operator on the - same field, the values of the existing condition are intersected with the - values of the new condition and the result is stored as the operator value. -- **Union**: if there already is a condition using the same operator on the - same field, the values of the new condition are added to the values of the - existing condition and the result is stored as the operator value. - -The following snippet demonstrates all of the strategies, using ``in`` as the -example operator: - -.. code-block:: ruby - - Band.in(name: ['a']).override.in(name: ['b']) - => #{"$in"=>["b"]}} - options: {} - class: Band - embedded: false> - - Band.in(name: ['a', 'b']).intersect.in(name: ['b', 'c']) - => #{"$in"=>["b"]}} - options: {} - class: Band - embedded: false> - - Band.in(name: ['a']).union.in(name: ['b']) - => #{"$in"=>["a", "b"]}} - options: {} - class: Band - embedded: false> - -The strategy is requested by calling ``override``, ``intersect`` or ``union`` -on a ``Criteria`` instance. The requested strategy applies to the next -condition method called on the query. If the next condition method called does -not support merge strategies, the strategy is reset, as shown in the following -example: - -.. code-block:: ruby - - Band.in(name: ['a']).union.ne(name: 'c').in(name: ['b']) - => #{"$in"=>["a"], "$ne"=>"c"}, "$and"=>[{"name"=>{"$in"=>["b"]}}]} - options: {} - class: Band - embedded: false> - -Since ``ne`` does not support merge strategies, the ``union`` strategy was -ignored and reset and when ``in`` was invoked the second time there was no -strategy active. - -.. warning:: - - Merge strategies currently assume the previous condition(s) have been added - to the top level of the query, however this is not always the case - (conditions may be nested under an ``$and`` clause). Using merge strategies - with complex criteria may cause incorrect queries to be constructed. - This misbehavior is `intended to be fixed in the future - `_. - - -Supported Operator Methods --------------------------- - -The following operator methods support merge strategies: - -- ``all`` -- ``in`` -- ``nin`` - -The set of methods may be expanded in future releases of Mongoid. For -future compatibility, only invoke a strategy method when the next method call -is an operator that supports merge strategies. - -Note that the merge strategies are currently only applied when conditions are -added through the designated methods. In the following example merge strategy -is not applied because the second condition is added via ``where``, not via -``in``: - -.. code-block:: ruby - - Band.in(foo: ['a']).union.where(foo: {'$in' => 'b'}) - => #{"$in"=>["a"]}, "$and"=>[{"foo"=>{"$in"=>"b"}}]} - options: {} - class: Band - embedded: false> - -This behavior may change in a future release of Mongoid and should not be -relied upon. - -In contrast, it does not matter how the existing query was built when a -merge strategy-supporting operator method is invoked. In the following -example, the first condition was added through ``where`` but the strategy -mechanism still applies: - -.. code-block:: ruby - - Band.where(foo: {'$in' => ['a']}).union.in(foo: ['b']) - => #{"$in"=>["a", "b"]}} - options: {} - class: Band - embedded: false> - -Operator Value Expansion ------------------------- - -Operator methods that support merge strategies all take ``Array`` as their value -type. Mongoid expands ``Array``-compatible types, such as a ``Range``, -when they are used with these operator methods: - -.. code-block:: ruby - - Band.in(year: 1950..1960) - => #{"$in"=>[1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960]}} - options: {} - class: Band - embedded: false> - -Additionally, Mongoid has historically wrapped non-``Array`` values in arrays, -as the following example demonstrates: - -.. code-block:: ruby - - Band.in(year: 1950) - => #{"$in"=>[1950]}} - options: {} - class: Band - embedded: false> - - -Query Methods -============= - -elem_match ----------- - -This matcher finds documents with array fields where one of the array values -matches all of the conditions. For example: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :tours, type: Array - end - - aerosmith = Band.create!(name: 'Aerosmith', tours: [ - {city: 'London', year: 1995}, - {city: 'New York', year: 1999}, - ]) - - Band.elem_match(tours: {city: 'London'}).to_a # => [aerosmith] - -``elem_match`` also works with embedded associations: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - embeds_many :tours - end - - class Tour - include Mongoid::Document - field :city, type: String - field :year, type: Integer - embedded_in :band - end - - dm = Band.create!(name: 'Depeche Mode') - aerosmith = Band.create!(name: 'Aerosmith') - Tour.create!(band: aerosmith, city: 'London', year: 1995) - Tour.create!(band: aerosmith, city: 'New York', year: 1999) - - Band.elem_match(tours: {city: 'London'}).to_a # => [aerosmith] - -``elem_match`` does not work with non-embedded associations because MongoDB -does not have joins - the conditions would be added to the collection -that is the source of a non-embedded association rather than the collection -of the association's target. - -``elem_match`` can also be used with recursively embedded associations, -as the following example shows: - -.. code-block:: ruby - - class Tag - include Mongoid::Document - field :name, type: String - recursively_embeds_many - end - - root = Tag.create!(name: 'root') - sub1 = Tag.new(name: 'sub1', child_tags: [Tag.new(name: 'subsub1')]) - root.child_tags << sub1 - root.child_tags << Tag.new(name: 'sub2') - root.save! - - Tag.elem_match(child_tags: {name: 'sub1'}).to_a # => [root] - - root.child_tags.elem_match(child_tags: {name: 'subsub1'}).to_a # => [sub1] - - -.. _projection: - -Projection -========== - -Mongoid provides two projection operators: ``only`` and ``without``. - - -.. _only: - -``only`` --------- - -The ``only`` method retrieves only the specified fields from the database. This -operation is sometimes called "projection". - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, type: String - field :label, type: String - - embeds_many :tours - end - - class Tour - include Mongoid::Document - - field :city, type: String - field :year, type: Integer - - embedded_in :band - end - - band = Band.only(:name).first - -Attempting to reference attributes which have not been loaded results in -``Mongoid::Errors::AttributeNotLoaded``. - -.. code-block:: ruby - - band.label - #=> raises Mongoid::Errors::AttributeNotLoaded - -Even though Mongoid currently allows writing to attributes that have not -been loaded, such writes will not be persisted -(`MONGOID-4701 `_) and -should therefore be avoided. - -``only`` can also be used with embedded associations: - -.. code-block:: ruby - - band = Band.only(:name, 'tours.year').last - # => # - - band.tours.first - # => # - -.. note:: - - Server versions 4.2 and lower allowed projecting both an association and - the association's fields in the same query, as follows: - - .. code-block:: ruby - - band = Band.only(:tours, 'tours.year').last - - The most recent projection specification overrides the earlier one. - For example, the above query was equivalent to: - - .. code-block:: ruby - - band = Band.only('tours.year').last - - Server versions 4.4 and higher prohibit specifying an association and its - fields in projection in the same query. - -``only`` can be specified with referenced associations (has_one, has_many, -has_and_belongs_to_many) but is currently ignored for referenced associations - -all fields of referenced associations will be loaded -(`MONGOID-4704 `_). - -Note that if a document has ``has_one`` or ``has_and_belongs_to_many`` associations, -the fields with foreign keys must be included in the list of attributes -loaded with ``only`` for those associations to be loaded. For example: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, type: String - - has_and_belongs_to_many :managers - end - - class Manager - include Mongoid::Document - - has_and_belongs_to_many :bands - end - - band = Band.create!(name: 'Astral Projection') - band.managers << Manager.new - - Band.where(name: 'Astral Projection').only(:name).first.managers - # => [] - - Band.where(name: 'Astral Projection').only(:name, :manager_ids).first.managers - # => [#] - - -.. _without: - -``without`` ------------ - -The opposite of ``only``, ``without`` causes the specified fields to be omitted: - -.. code-block:: ruby - - Band.without(:name) - # => - # #{"name"=>0}} - # class: Band - # embedded: false> - -Because Mongoid requires the ``_id`` field for various operations, it (as well -as its ``id`` alias) cannot be omitted via ``without``: - -.. code-block:: ruby - - Band.without(:name, :id) - # => - # #{"name"=>0}} - # class: Band - # embedded: false> - - Band.without(:name, :_id) - # => - # #{"name"=>0}} - # class: Band - # embedded: false> - - -.. _ordering: - -Ordering -======== - -Mongoid provides the ``order`` method on ``Criteria`` objects and its alias, -``order_by``, to specify the ordering of documents. These methods take a -hash indicating which fields to order the documents by, and whether to use -ascending or descending order for each field. - -.. code-block:: ruby - - Band.order(name: 1) - # => #{"name"=>1}} - # class: Band - # embedded: false> - - Band.order_by(name: -1, description: 1) - # => #{"name"=>-1, "description"=>1}} - # class: Band - # embedded: false> - - Band.order_by(name: :desc, description: 'asc') - # => #{"name"=>-1, "description"=>1}} - # class: Band - # embedded: false> - -The direction may be specified as integers ``1`` and ``-1`` for ascending -and descending, respectively, or as symbols ``:asc`` and ``:desc``, or as -strings ``"asc"`` and ``"desc"``. - -Alternatively, ``order`` accepts an array of two-element arrays specifying -the ordering. Field names and directions may be strings or symbols. - -.. code-block:: ruby - - Band.order([['name', 'desc'], ['description', 'asc']]) - - Band.order([[:name, :desc], [:description, :asc]]) - -Another way of providing the order is to use ``#asc`` and ``#desc`` methods -on symbols, as follows: - -.. code-block:: ruby - - Band.order(:name.desc, :description.asc) - -The arguments can be provided as a string using SQL syntax: - -.. code-block:: ruby - - Band.order('name desc, description asc') - -Finally, there are ``asc`` and ``desc`` methods that can be used instead of -``order``/``order_by``: - -.. code-block:: ruby - - Band.asc('name').desc('description') - # => #{"name"=>1, "description"=>-1}} - class: Band - embedded: false> - -``order`` calls can be chained, in which case the oldest calls define the -most significant criteria and the newest calls define the least significant -ones (since in Ruby hashes maintain the order of their keys): - -.. code-block:: ruby - - Band.order('name desc').order('description asc') - # => #{"name"=>-1, "description"=>1}} - class: Band - embedded: false> - -This can sometimes lead to surprising results if there are scopes, including -the default scope, that use ``order``/``order_by``. For example, in the -following snippet bands are ordered by name first because the order in the -default scope takes precedence over the order given in the query, due to -the default scope being evaluated first: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, type: String - field :year, type: Integer - - default_scope -> { order(name: :asc) } - end - - Band.order(year: :desc) - # => #{"name"=>1, "year"=>-1}} - class: Band - embedded: false> - - -Pagination -========== - -Mongoid provides the pagination operators ``limit``, ``skip``, and ``batch_size`` on ``Criteria``. - -.. _limit: - -``limit`` ---------- - -``limit`` sets the total number of documents to be returned by a query: - -.. code-block:: ruby - - Band.limit(5) - # => - # #5} - # class: Band - # embedded: false> - -.. _skip: - -``skip`` --------- - -``skip`` (alias: ``offset``) sets the number of query results to skip -before returning documents. The ``limit`` value, if specified, will be applied -after documents are skipped. When performing pagination, ``skip`` is recommended -to be combined with :ref:`ordering ` to ensure consistent results. - -.. code-block:: ruby - - Band.skip(10) - # => - # #10} - # class: Band - # embedded: false> - -.. _batch-size: - -``batch_size`` --------------- - -When executing large queries, or when iterating over query results with an enumerator method such as -``Criteria#each``, Mongoid automatically uses the `MongoDB getMore command -`_ to load results in batches. -The default ``batch_size`` is 1000, however you may set it explicitly: - -.. code-block:: ruby - - Band.batch_size(500) - # => - # #500} - # class: Band - # embedded: false> - - -Finding By ``_id`` -================== - -Mongoid provides the ``find`` method on ``Criteria`` objects to find documents -by their ``_id`` values: - -.. code-block:: ruby - - Band.find('5f0e41d92c97a64a26aabd10') - # => # - -The ``find`` method performs type conversion, if necessary, of the argument -to the type declared in the model being queried for the ``_id`` field. -By default, the ``_id`` type is ``BSON::ObjectId``, thus the query above -is equivalent to: - -.. code-block:: ruby - - Band.find(BSON::ObjectId.from_string('5f0e41d92c97a64a26aabd10')) - # => # - -.. note:: - - When querying collections directly using the driver, type conversion is not - automatically performed: - -.. code-block:: ruby - - Band.collection.find(_id: BSON::ObjectId.from_string('5f0e41d92c97a64a26aabd10')).first - # => {"_id"=>BSON::ObjectId('5f0e41d92c97a64a26aabd10'), "name"=>"Juno Reactor"} - - Band.collection.find(_id: '5f0e41d92c97a64a26aabd10').first - # => nil - -The ``find`` method can accept multiple arguments, or an array of arguments. -In either case each of the arguments or array elements is taken to be an ``_id`` -value, and documents with all of the specified ``_id`` values are returned in -an array: - -.. code-block:: ruby - - Band.find('5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e') - # => [#, - #] - - Band.find(['5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e']) - # => [#, - #] - -If the same ``_id`` value is given more than once, the corresponding document -is only returned once: - -.. code-block:: ruby - - Band.find('5f0e41b02c97a64a26aabd0e', '5f0e41b02c97a64a26aabd0e') - # => [#] - -The documents returned are *not* ordered, and may be returned in a different -order from the order of provided ``_id`` values, as illustrated in the above -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. -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 -and some are not found, the return value is an array of documents that were -found (which could be empty if no documents were found at all). - - -.. _additional-query-methods: - -Additional Query Methods -======================== - -Mongoid also has some helpful methods on criteria. - -.. list-table:: - :header-rows: 1 - :widths: 30 60 - - * - Operation - - Example - - * - ``Criteria#count`` - - *Get the total number of documents matching a filter, or the total - number of documents in a collection. Note this will always hit - the database for the count.* - - *As of Mongoid 7.2, the* ``count`` *method uses the* - ``count_documents`` *driver helper to obtain the accurate count. - previously the* ``count`` *driver helper was used which used - collection metadata and was thus not necessarily accurate (but - may have returned the result faster). Use* ``estimated_count`` - *method to obtain an approximate number of documents in the collection - quickly.* - - - - .. code-block:: ruby - - Band.count - Band.where(name: "Photek").count - - * - ``Criteria#estimated_count`` - - *Get an approximate number of documents in the collection using the - collection metadata. The* ``estimated_count`` *method does not accept - query conditions; if any are given, it will raise* - ``Mongoid::Errors::InvalidEstimatedCountCriteria``. - *If a model defines a default scope,* ``estimated_count`` *must be - called on the unscoped model*. - - - - .. code-block:: ruby - - Band.count - Band.where(name: "Photek").count - - class Contract - include Mongoid::Document - - field :active, type: Boolean - - default_scope -> { where(active: true) } - end - - Contract.estimated_count - # => raises Mongoid::Errors::InvalidEstimatedCountCriteria - - Contract.unscoped.estimated_count - # => 0 - - * - ``Criteria#distinct`` - - *Get a list of distinct values for a single field. Note this will always hit - the database for the distinct values.* - - *This method accepts the dot notation, thus permitting referencing - fields in embedded associations.* - - *This method respects :ref:`field aliases `, - including those defined in embedded documents.* - - - - .. code-block:: ruby - - Band.distinct(:name) - Band.where(:fans.gt => 100000). - distinct(:name) - - Band.distinct('cities.name') - - # Assuming an aliased field: - class Manager - include Mongoid::Document - embedded_in :band - field :name, as: :n - end - - # Expands out to "managers.name" in the query: - Band.distinct('managers.n') - - * - ``Criteria#each`` - - *Iterate over all matching documents in the criteria.* - - - - .. code-block:: ruby - - Band.where(members: 1).each do |band| - p band.name - end - - * - ``Criteria#exists?`` - - *Determine if any matching documents exist. Will return true if there - are 1 or more.* - - ``#exists?`` *now takes a number of argument types:* - - - ``Hash``: *A hash of conditions.* - - ``Object``: *An _id to search for.* - - ``false``/``nil``: *Always returns false.* - - - - .. code-block:: ruby - - Band.exists? - Band.where(name: "Photek").exists? - Band.exists?(name: "Photek") - Band.exists?(BSON::ObjectId('6320d96a3282a48cfce9e72c')) - Band.exists?('6320d96a3282a48cfce9e72c') - Band.exists?(false) - Band.exists?(nil) - - * - ``Criteria#fifth`` - - *Get the fifth document for the given criteria.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.fifth - - * - ``Criteria#fifth!`` - - *Get the fifth document for the given criteria, or raise an error if - none exist.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.fifth! - - * - ``Criteria#find_by`` - - *Find a document by the provided attributes. If not found, - raise an error or return nil depending on the value of the* - ``raise_not_found_error`` *configuration option.* - - - - .. code-block:: ruby - - Band.find_by(name: "Photek") - - Band.find_by(name: "Tool") do |band| - band.impressions += 1 - end - - * - ``Criteria#find_or_create_by`` - - *Find a document by the provided attributes, and if not found - create and return a newly persisted one. Note that attributes provided in the arguments to - this method will override any set in ``create_with``*. - - - - .. code-block:: ruby - - Band.find_or_create_by(name: "Photek") - Band.where(:likes.gt => 10).find_or_create_by(name: "Photek") - - ``find_or_create_by`` can be used on any scope, but in this case - the criteria given by the scope and by ``find_or_create_by`` are - combined. The following creates three bands: - - .. code-block:: ruby - - Band.find_or_create_by(name: "Photek") - Band.where(name: "Photek").find_or_create_by(name: "Aerosmith") - # creates Aerosmith again because there is no band whose name - # is Photek and Aerosmith at the same time - Band.where(name: "Photek").find_or_create_by(name: "Aerosmith") - - * - ``Criteria#find_or_initialize_by`` - - *Find a document by the provided attributes, and if not found - return a new one.* - - - - .. code-block:: ruby - - Band.find_or_initialize_by(name: "Photek") - Band.where(:likes.gt => 10).find_or_initialize_by(name: "Photek") - - * - ``Criteria#first|last`` - - *Finds a single document given the provided criteria. Get a list of - documents by passing in a limit argument. This method automatically adds - a sort on _id. This can cause performance issues, so if the sort is - undesirable, Criteria#take can be used instead.* - - - - .. code-block:: ruby - - Band.first - Band.where(:members.with_size => 3).first - Band.where(:members.with_size => 3).last - Band.first(2) - - * - ``Criteria#first!|last!`` - - *Finds a single document given the provided criteria, or raises an error - if none are found. This method automatically adds a sort on _id if no - sort is given. This can cause performance issues, so if the sort is - undesirable, Criteria#take! can be used instead.* - - - - .. code-block:: ruby - - Band.first! - Band.where(:members.with_size => 3).first! - Band.where(:members.with_size => 3).last! - - * - ``Criteria#first_or_create`` - - *Find the first document by the provided attributes, and if not found - create and return a newly persisted one.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").first_or_create - - * - ``Criteria#first_or_create!`` - - *Find the first document by the provided attributes, and if not found - create and return a newly persisted one using* ``create!``. - - - - .. code-block:: ruby - - Band.where(name: "Photek").first_or_create! - - * - ``Criteria#first_or_initialize`` - - *Find the first document by the provided attributes, and if not found - return a new one.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").first_or_initialize - - * - ``Criteria#for_js`` - - *Find documents for a provided JavaScript expression, optionally with - the specified variables added to the evaluation scope. The scope - argument is supported in MongoDB 4.2 and lower.* - *Prefer* :manual:`$expr ` *over* ``for_js``. - - - - .. code-block:: ruby - - # All MongoDB versions - Band.for_js("this.name = 'Tool'") - - # MongoDB 4.2 and lower - Band.for_js("this.name = param", param: "Tool") - - * - ``Criteria#fourth`` - - *Get the fourth document for the given criteria.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.fourth - - * - ``Criteria#fourth!`` - - *Get the fourth document for the given criteria, or raise an error if - none exist.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.fourth! - - * - ``Criteria#length|size`` - - *Same as count but caches subsequent calls to the database* - - - - .. code-block:: ruby - - Band.length - Band.where(name: "FKA Twigs").size - - * - ``Criteria#pick`` - - *Get the values from one document for the provided fields. - Returns nil for unset fields and for non-existent fields.* - - *This method does not apply a sort to the documents, so it - will not necessarily return the values from the first document.* - - *This method accepts the dot notation, thus permitting referencing - fields in embedded associations.* - - *This method respects :ref:`field aliases `, - including those defined in embedded documents.* - - - - .. code-block:: ruby - - Band.all.pick(:name) - - Band.all.pick('cities.name') - - # Using the earlier definition of Manager, - # expands out to "managers.name" in the query: - Band.all.pick('managers.n') - - - * - ``Criteria#pluck`` - - *Get all the values for the provided field. - Returns nil for unset fields and for non-existent fields.* - - *This method accepts the dot notation, thus permitting referencing - fields in embedded associations.* - - *This method respects :ref:`field aliases `, - including those defined in embedded documents.* - - - - .. code-block:: ruby - - Band.all.pluck(:name) - #=> ["Daft Punk", "Aphex Twin", "Ween"] - - Band.all.pluck('address.city') - #=> ["Paris", "Limerick", "New Hope"] - - # Using the earlier definition of Manager, - # expands out to "managers.name" in the query: - Band.all.pluck('managers.n') - #=> [ ["Berry Gordy", "Tommy Mottola"], [], ["Quincy Jones"] ] - - # Accepts multiple field arguments, in which case - # the result will be returned as an Array of Arrays. - Band.all.pluck(:name, :likes) - #=> [ ["Daft Punk", 342], ["Aphex Twin", 98], ["Ween", 227] ] - - * - ``Criteria#read`` - - *Sets the read preference for the criteria.* - - - - .. code-block:: ruby - - Band.all.read(mode: :primary) - - * - ``Criteria#second`` - - *Get the second document for the given criteria.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.second - - * - ``Criteria#second!`` - - *Get the second document for the given criteria, or raise an error if - none exist.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.second! - - * - ``Criteria#second_to_last`` - - *Get the second to last document for the given criteria.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.second_to_last - - * - ``Criteria#second_to_last!`` - - *Get the second to last document for the given criteria, or raise an - error if none exist.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.second_to_last! - - * - ``Criteria#take`` - - *Get a list of n documents from the database or just one if no parameter - is provided.* - - *This method does not apply a sort to the documents, so it can return - different document(s) than #first and #last.* - - - - .. code-block:: ruby - - Band.take - Band.take(5) - - * - ``Criteria#take!`` - - *Get a document from the database or raise an error if none exist.* - - *This method does not apply a sort to the documents, so it can return - different document(s) than #first and #last.* - - - - .. code-block:: ruby - - Band.take! - - * - ``Criteria#tally`` - - *Get a mapping of values to counts for the provided field.* - - *This method accepts the dot notation, thus permitting referencing - fields in embedded associations.* - - *This method respects :ref:`field aliases `, - including those defined in embedded documents.* - - - - .. code-block:: ruby - - Band.all.tally(:name) - - Band.all.tally('cities.name') - - # Using the earlier definition of Manager, - # expands out to "managers.name" in the query: - Band.all.tally('managers.n') - - * - ``Criteria#third`` - - *Get the third document for the given criteria.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.third - - * - ``Criteria#third!`` - - *Get the third document for the given criteria, or raise an error if - none exist.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.third! - - * - ``Criteria#third_to_last`` - - *Get the third to last document for the given criteria.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.third_to_last - - * - ``Criteria#third_to_last!`` - - *Get the third to last document for the given criteria, or raise an - error if none exist.* - - *This method automatically adds a sort on _id if no sort is given.* - - - - .. code-block:: ruby - - Band.third_to_last! - - -Eager Loading -============= - -Mongoid provides a facility to eager load documents -from associations to prevent the n+1 issue when -iterating over documents with association access. Eager loading is supported on -all associations with the exception of polymorphic ``belongs_to`` -associations. - -.. code-block:: ruby - - class Band - include Mongoid::Document - has_many :albums - end - - class Album - include Mongoid::Document - belongs_to :band - end - - Band.includes(:albums).each do |band| - p band.albums.first.name # Does not hit the database again. - end - - -Regular Expressions -=================== - -MongoDB, and Mongoid, allow querying documents by regular expressions. - -Given the following model definitions: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, type: String - field :description, type: String - end - - Band.create!(name: 'Sun Project', description: "Sun\nProject") - -... we can query using simple Ruby regular expressions in a natural way: - -.. code-block:: ruby - - Band.where(name: /project/i).first - # => # - -It is also possible to query using PCRE syntax by constructing -``BSON::Regexp::Raw`` objects explicitly: - -.. code-block:: ruby - - Band.where(description: /\AProject/).first - # => # - - Band.where(description: BSON::Regexp::Raw.new('^Project')).first - # => nil - - Band.where(description: BSON::Regexp::Raw.new('^Project', 'm')).first - # => # - - -Conditions On Fields -==================== - -When a condition uses a field defined in the model, the value being specified -in the condition is converted according to the rules of the field, if any. -For example, consider the following model definition that contains a ``Time`` -field, a ``Date`` field and an implicit ``Object`` field, and also -intentionally does not define a field called ``deregistered_at``: - -.. code-block:: ruby - - class Voter - include Mongoid::Document - - field :born_on, type: Date - field :registered_at, type: Time - field :voted_at - end - -Queries on ``born_on`` and ``registered_at`` fields using ``Date`` and ``Time`` -values, respectively, are straightforward: - -.. code-block:: ruby - - Voter.where(born_on: Date.today).selector - # => {"born_on"=>2020-12-18 00:00:00 UTC} - - Voter.where(registered_at: Time.now).selector - # => {"registered_at"=>2020-12-19 04:33:36.939788067 UTC} - -But, note the differences in behavior when providing a ``Date`` instance -in all possible scenarios: - -.. code-block:: ruby - - Voter.where(born_on: Date.today).selector - # => {"born_on"=>2020-12-18 00:00:00 UTC} - - Voter.where(registered_at: Date.today).selector - # => {"registered_at"=>2020-12-18 00:00:00 -0500} - - Voter.where(voted_at: Date.today).selector - # => {"voted_at"=>Fri, 18 Dec 2020} - - Voter.where(deregistered_at: Date.today).selector - # => {"deregistered_at"=>2020-12-18 00:00:00 UTC} - -When using the ``registered_at`` field which is of type ``Time``, the date -was interpreted to be in local time (as per the :ref:`configured time zone -`). When using the ``born_on`` field which is of type ``Date``, -the date was interpreted to be in UTC. When using the ``voted_at`` field -which was defined without a type (hence implicitly as an ``Object``), -the date was used unmodified in the constructed query. When using a -nonexistent field ``deregistered_at`` the date was interpreted to be in UTC -and converted to a time, matching the behavior of querying a ``Date`` field. - - -Scoping -======= - -Scopes provide a convenient way to reuse common criteria with more -business domain style syntax. - - -.. _named-scopes: - -Named Scopes ------------- - -Named scopes are simply criteria defined at class load that are referenced -by a provided name. Just like normal criteria, they are lazy and chainable. - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :country, type: String - field :genres, type: Array - - scope :english, ->{ where(country: "England") } - scope :rock, ->{ where(:genres.in => [ "rock" ]) } - end - - Band.english.rock # Get the English rock bands. - -Named scopes can take procs and blocks for accepting parameters or -extending functionality. - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :country, type: String - field :active, type: Boolean, default: true - - scope :named, ->(name){ where(name: name) } - scope :active, ->{ - where(active: true) do - def deutsch - tap do |scope| - scope.selector.store("origin" => "Deutschland") - end - end - end - } - end - - Band.named("Depeche Mode") # Find Depeche Mode. - Band.active.deutsch # Find active German bands. - -By default, Mongoid allows defining a scope that would shadow an existing -class method, as the following example shows: - -.. code-block:: ruby - - class Product - include Mongoid::Document - - def self.fresh - true - end - - scope :fresh, ->{ where(fresh: true) } - end - -To have Mongoid raise an error when a scope would overwrite an existing class -method, set the ``scope_overwrite_exception`` :ref:`configuration option -` to ``true``. - - -Default Scopes --------------- - -Default scopes can be useful when you find yourself applying the same -criteria to most queries, and wish to specify these criteria as the default. -Default scopes are procs that return criteria objects. - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :active, type: Boolean - - default_scope ->{ where(active: true) } - end - - Band.each do |band| - # All bands here are active. - end - -Specifying a default scope also initializes the fields of new models to -the values given in the default scope, if the values are simple literals: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :active, type: Boolean - field :num_tours, type: Integer - - default_scope ->{ where(active: true, num_tours: {'$gt' => 1}) } - end - - # active is set, num_tours is not set - Band.new # => # - -Note that if a default value is provided both in the field definition and -in the default scope, the value in the default scope takes precedence: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :active, type: Boolean, default: true - - default_scope ->{ where(active: false) } - end - - Band.new # => # - -Because a default scope initializes fields in new models as just described, -defining a default scope with a dotted key and a simple literal value, while -possible, is not recommended: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :tags, type: Hash - - default_scope ->{ where('tags.foo' => 'bar') } - end - - Band.create! - # => Created document: {"_id"=>BSON::ObjectId('632de48f3282a404bee1877b'), "tags.foo"=>"bar"} - Band.create!(tags: { 'foo' => 'bar' }) - # => Created document: {"_id"=>BSON::ObjectId('632de4ad3282a404bee1877c'), "tags.foo"=>"bar", "tags"=>{"foo"=>"bar"}} - Band.all.to_a - # => [ #"bar"}> ] - -Mongoid 8 allows dotted keys to be used in Mongoid, and when creating a document, -the scope is added as a dotted key in the attributes: - -.. code-block:: ruby - - Band.new.attribute - # => {"_id"=>BSON::ObjectId('632de97d3282a404bee1877d'), "tags.foo"=>"bar"} - -Whereas when querying, Mongoid looks for an embedded document: - -.. code-block:: ruby - - Band.create! - # => Created document: {"_id"=>BSON::ObjectId('632de48f3282a404bee1877b'), "tags.foo"=>"bar"} - Band.where - # => #"bar"} - options: {} - class: Band - embedded: false> - # This looks for something like: { tags: { "foo" => "bar" } } - Band.count - # => 0 - -A workaround is to define the default scope as a complex query: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :tags, type: Hash - - default_scope ->{ where('tags.foo' => {'$eq' => 'bar'}) } - end - - Band.create!(tags: { hello: 'world' }) - Band.create!(tags: { foo: 'bar' }) - # does not add a "tags.foo" dotted attribute - Band.count - # => 1 - -You can tell Mongoid not to apply the default scope by using -``unscoped``, which can be inline or take a block. - -.. code-block:: ruby - - Band.unscoped.where(name: "Depeche Mode") - Band.unscoped do - Band.where(name: "Depeche Mode") - end - -You can also tell Mongoid to explicitly apply the default scope -again later to always ensure it's there. - -.. code-block:: ruby - - Band.unscoped.where(name: "Depeche Mode").scoped - -If you are using a default scope on a model that is part of an association, -you must reload the association to have scoping reapplied. -This is important to note if you change a value of a document in the association -that would affect its visibility within the scoped association. - -.. code-block:: ruby - - class Label - include Mongoid::Document - embeds_many :bands - end - - class Band - include Mongoid::Document - field :active, default: true - embedded_in :label - default_scope ->{ where(active: true) } - end - - label.bands.push(band) - label.bands # [ band ] - band.update_attribute(:active, false) - label.bands # [ band ] Must reload. - label.reload.bands # [] - -.. note:: - - After the default scope is applied, it is no longer distinguished from - other query conditions. This can lead to surprising behavior when using - ``or`` and ``nor`` operators in particular: - - .. code-block:: ruby - - class Band - include Mongoid::Document - - field :name - field :active - field :touring - - default_scope ->{ where(active: true) } - end - - Band.where(name: 'Infected Mushroom') - # => - # #true, "name"=>"Infected Mushroom"} - # options: {} - # class: Band - # embedded: false> - - Band.where(name: 'Infected Mushroom').or(touring: true) - # => - # #[{"active"=>true, "name"=>"Infected Mushroom"}, {"touring"=>true}]} - # options: {} - # class: Band - # embedded: false> - - Band.or(touring: true) - # => - # #[{"active"=>true}, {"touring"=>true}]} - # options: {} - # class: Band - # embedded: false> - - In the last example, you might expect the two conditions - (``active: true`` and ``touring: true``) to be combined with an ``$and``, - but because the ``Band`` class already has the scope applied to it, - it becomes one of the disjunction branches of the ``or``. - - -Runtime Default Scope Override ------------------------------- - -You can use the ``with_scope`` method to change the default scope in a block -at runtime: - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :country, type: String - field :genres, type: Array - - scope :english, ->{ where(country: "England") } - end - - criteria = Band.with_scope(Band.english) do - Band.all - end - - criteria - # => - # #"England"} - # options: {} - # class: Band - # embedded: false> - -.. note:: - - If with_scope calls are nested, when the nested with_scope block completes - Mongoid 7 sets the current scope to nil instead of the parent scope. - Mongoid 8 will set the current scope to the correct parent scope. - To get Mongoid 8 behavior in Mongoid 7.4 and higher, set the - ``Mongoid.broken_scoping`` global option to false. - - -Class Methods -------------- - -Class methods on models that return criteria objects are also -treated like scopes, and can be chained as well. - -.. code-block:: ruby - - class Band - include Mongoid::Document - field :name, type: String - field :active, type: Boolean, default: true - - def self.active - where(active: true) - end - end - - Band.active - - -Queries + Persistence -===================== - -Mongoid supports persistence operations off of criteria -in a light capacity for when you want to expressively perform multi -document inserts, updates, and deletion. - -.. warning:: - - Criteria ordering and pagination conditions, including ``order``, ``limit``, - ``offset``, and ``batch_size``, will be ignored on the following operations. - -.. list-table:: - :header-rows: 1 - :widths: 30 60 - - * - Operation - - Example - - * - ``Criteria#create`` - - *Create a newly persisted document.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").create - - * - ``Criteria#create!`` - - *Create a newly persisted document and raise an exception on validation failure.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").create! - - * - ``Criteria#build|new`` - - *Create a new (unsaved) document.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").build - Band.where(name: "Photek").new - - * - ``Criteria#update`` - - *Update attributes of the first matching document.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").update(label: "Mute") - - * - ``Criteria#update_all`` - - *Update attributes of all matching documents.* - - - - .. code-block:: ruby - - Band.where(members: 2).update_all(label: "Mute") - - * - ``Criteria#add_to_set`` - - *Perform an $addToSet on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").add_to_set(label: "Mute") - - * - ``Criteria#bit`` - - *Perform a $bit on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").bit(likes: { and: 14, or: 4 }) - - * - ``Criteria#inc`` - - *Perform an $inc on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").inc(likes: 123) - - * - ``Criteria#pop`` - - *Perform a $pop on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Photek").pop(members: -1) - Band.where(name: "Photek").pop(members: 1) - - * - ``Criteria#pull`` - - *Perform a $pull on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Tool").pull(members: "Maynard") - - * - ``Criteria#pull_all`` - - *Perform a $pullAll on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Tool"). - pull_all(:members, [ "Maynard", "Danny" ]) - - * - ``Criteria#push`` - - *Perform a $push on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Tool").push(members: "Maynard") - - * - ``Criteria#push_all`` - - *Perform a $push with $each on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Tool"). - push_all(members: [ "Maynard", "Danny" ]) - - * - ``Criteria#rename`` - - *Perform a $rename on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Tool").rename(name: :title) - - * - ``Criteria#set`` - - *Perform a $set on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Tool").set(likes: 10000) - - * - ``Criteria#unset`` - - *Perform a $unset on all matching documents.* - - - - .. code-block:: ruby - - Band.where(name: "Tool").unset(:likes) - - * - ``Criteria#delete`` - - *Deletes all matching documents in the database.* - - - - .. code-block:: ruby - - Band.where(label: "Mute").delete - - * - ``Criteria#destroy`` - - *Deletes all matching documents in the database while running callbacks for all. - This loads all documents into memory and can be an expensive operation.* - - - - .. code-block:: ruby - - Band.where(label: "Mute").destroy - - -.. _query-cache: - -Query Cache -=========== - -The Ruby MongoDB driver versions 2.14 and above provide query caching functionality. When enabled, the -query cache saves the results of previously executed find and aggregation -queries and reuses them in the future instead of performing the queries again, -thus increasing application performance and reducing database load. - -Please review the `driver query cache documentation -`_ -for details about the driver's query cache behavior. - -The rest of this section assumes that driver 2.14.0 or later is being used. - - -Enabling Query Cache --------------------- - -The query cache may be enabled by using the driver's namespace or Mongoid's -namespace. - - -.. _enabling-query-cache-automatically: - -Enabling Query Cache Automatically ----------------------------------- - -The MongoDB Ruby Driver provides middleware to automatically enable the query cache for -Rack web requests and ActiveJob job runs. Please see the :ref:`Query Cache Rack Middleware -` section on the configuration page for instructions. - -Note that the Query Cache Middleware does not apply to code executed outside web requests -and/or jobs. - - -.. _enabling-query-cache-manually: - -Enabling Query Cache Manually ------------------------------ - -To enable the Query Cache manually for a code segment, use: - -.. code-block:: ruby - - Mongo::QueryCache.cache do - # ... - end - -The Query Cache can also be explicitly enabled and disabled, although we -recommend to use the block form described above: - -.. code-block:: ruby - - begin - Mongo::QueryCache.enabled = true - # ... - ensure - Mongo::QueryCache.enabled = false - end - - -.. _query-cache-first-method: - -Caching the Result of ``#first`` --------------------------------- - -Calling the ``first`` method on a model class imposes an ascending sort by -the ``_id`` field on the underlying query. This may produce unexpected behavior -with query caching. - -For example, when calling ``all`` on a model class and then ``first``, -one would expect the second query to use the cached results from the first. -However, because of the sort imposed on the second query, both methods -will query the database and separately cache their results. - -.. code-block:: ruby - - Band.all.to_a - #=> Queries the database and caches the results - - Band.first - #=> Queries the database again because of the sort - -To use the cached results, call ``all.to_a.first`` on the model class. - - -.. _load-async: - -Asynchronous Queries -==================== - -Mongoid allows running database queries asynchronously in the background. -This can be beneficial when there is a need to get documents from different -collections. - -In order to schedule an asynchronous query call the ``load_async`` method on a -``Criteria``: - -.. code-block:: ruby - - class PagesController < ApplicationController - def index - @active_bands = Band.where(active: true).load_async - @best_events = Event.best.load_async - @public_articles = Article.where(public: true).load_async - end - end - -In the above example three queries will be scheduled for asynchronous execution. -Results of the queries can be later accessed as usual: - -.. code-block:: html - -
    - <%- @active_bands.each do -%> -
  • <%= band.name %>
  • - <%- end -%> -
- -Even if a query is scheduled for asynchronous execution, it might be executed -synchronously on the caller's thread. There are three possible scenarios depending -on when the query results are being accessed: - -#. If the scheduled asynchronous task has been already executed, the results are returned. -#. If the task has been started, but not finished yet, the caller's thread blocks until the task is finished. -#. If the task has not been started yet, it is removed from the execution queue, and the query is executed synchronously on the caller's thread. - -.. note:: - - Even though ``load_async`` method returns a ``Criteria`` object, you should not - do any operations on this object except accessing query results. The query is - scheduled for execution immediately after calling ``load_async``, therefore - later changes to the ``Criteria`` object may not be applied. - - -Configuring asynchronous query execution ----------------------------------------- - -Asynchronous queries are disabled by default. When asynchronous queries are -disabled, ``load_async`` will execute the query immediately on the current thread, -blocking as necessary. Therefore, calling ``load_async`` on criteria in this case -is roughly the equivalent of calling ``to_a`` to force query execution. - -In order to enable asynchronous query execution, the following config options -must be set: - -.. code-block:: yaml - - development: - ... - options: - # Execute asynchronous queries using a global thread pool. - async_query_executor: :global_thread_pool - # Number of threads in the pool. The default is 4. - # global_executor_concurrency: 4 diff --git a/source/reference/sessions.txt b/source/reference/sessions.txt deleted file mode 100644 index 4faf35c0..00000000 --- a/source/reference/sessions.txt +++ /dev/null @@ -1,56 +0,0 @@ -.. _sessions: - -******** -Sessions -******** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -You can use sessions with Mongoid in a similar way that you would execute a transaction in ActiveRecord. -Namely, you can call a method, ``#with_session`` on a model class or on an instance of a model and execute -some operations in a block. All operations in the block will be executed in the context of single session. -Please see the MongoDB Ruby driver documentation for what session options are available. - -Please note the following limitations of sessions: - -- Sessions cannot be shared across threads; sessions are not thread-safe. This is consistent with the Ruby driver's support for sessions. - -- Sessions cannot be nested. You cannot called ``#with_session`` on a model class or a model instance within the block passed to the ``#with_session`` method on another model class or model instance. - -- All model classes and instances used within the session block must use the same driver client. For example, if you have specified different ``storage_options`` for another model used in the block than that of the model class or instance on which ``#with_session`` is called, you will get an error. - -Using a Session via Model#with_session -====================================== - -Call ``#with_session`` on a model class and pass it session options to execute a block in the context -of a session. - -.. code-block:: ruby - - Person.with_session(causal_consistency: true) do - Person.create! - person = Person.first - person.name = "Emily" - person.save - end - - -Using a Session via model#with_session -====================================== - -Call ``#with_session`` on a model instance and pass it session options to execute a block in the context -of a session. - -.. code-block:: ruby - - person.with_session(causal_consistency: true) do - person.username = 'Emily' - person.save - person.posts << Post.create! - end diff --git a/source/reference/sharding.txt b/source/reference/sharding.txt deleted file mode 100644 index 6fd41522..00000000 --- a/source/reference/sharding.txt +++ /dev/null @@ -1,146 +0,0 @@ -.. _sharding: - -********************** -Sharding Configuration -********************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -Mongoid can assist with setting up collection sharding in sharded environments. - - -.. _shard-keys: - -Declaring Shard Keys -==================== - -Shard keys can be declared on models using the ``shard_key`` macro: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - field :ssn - - shard_key ssn: 1 - - # The collection must also have an index that starts with the shard key. - index ssn: 1 - end - -Note that in order to shard a collection, the collection must have an index -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 -follows the format of MongoDB `shardCollection shell helper -`_ -and allows specifying ranged and hashed shard keys, compound shard keys and -collection sharding options: - -.. code-block:: ruby - - shard_key ssn: 1 - - shard_key ssn: 1, country: 1 - - shard_key ssn: :hashed - - shard_key {ssn: :hashed}, unique: true - -The alternative is the shorthand syntax, in which only the keys are given. -This syntax only supports ranged shard keys and does not allow options to -be specified: - -.. code-block:: ruby - - shard_key :ssn - - shard_key :ssn, :country - -``shard_key`` macro can take the name of a ``belongs_to`` association in -place of a field name, in which case Mongoid will use the foreign key -configured in the association as the field name: - -.. code-block:: ruby - - class Person - include Mongoid::Document - - belongs_to :country - - # Shards by country_id. - shard_key country: 1 - - # The collection must also have an index that starts with the shard key. - index country: 1 - end - -The shard key may also reference a field in an embedded document, by using -the "." character to delimit the field names: - -.. code-block:: ruby - - shard_key "location.x" => 1, "location.y" => 1 - - shard_key "location.x", "location.y" - -.. note:: - - Because the "." character is used to delimit fields in embedded documents, - Mongoid does not currently support shard key fields that themselves - literally contain the "." character. - -.. note:: - - If a model declares a shard key, Mongoid expects the respective collection - to be sharded with the specified shard key. When reloading models, Mongoid - will provide the shard key in addition to the ``id`` field value to the - ``find`` command to improve query performance, especially on `geographically - distributed sharded clusters `_. - If the collection is not sharded with the specified shard key, queries - may produce incorrect results. - - -.. _sharding-management: - -Sharding Management Rake Tasks -============================== - -To shard collections in the database according to the shard keys defined in -the models, run the ``db:mongoid:shard_collections`` Rake task. -If necessary, run the ``db:mongoid:create_indexes`` Rake task prior to -sharding collections: - -.. code-block:: bash - - rake db:mongoid:create_indexes - rake db:mongoid:shard_collections - -.. note:: - - Like with index management rake tasks, sharding management rake tasks - generally do not stop and fail when they encounter the problem with a - particular model class. Instead they log the problem (to the configured - Mongoid logger) at an appropriate level and continue with the next model. - When Mongoid is used in a Rails application, this means the results of - the rake task execution will generally be found in the per-environment - log file like ``log/development.log``. - -.. note:: - - When performing schema-related operations in a sharded cluster, such as - sharding collections as described in this document, or creating or dropping - collections or databases, cluster nodes may end up with out of date local - configuration-related cache data. Execute the `flushRouterConfig - `_ - command on each ``mongos`` node to clear these caches. diff --git a/source/reference/text-search.txt b/source/reference/text-search.txt deleted file mode 100644 index 9624affb..00000000 --- a/source/reference/text-search.txt +++ /dev/null @@ -1,85 +0,0 @@ -.. _mongoid-text-search: -.. _text-search: - -*********** -Text Search -*********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - - -MongoDB provides :manual:`text indexes ` -to support text search queries on string content. Text indexes -can include any field whose value is a string or an array of -string elements. - -.. note:: - - MongoDB Atlas also provides - `Atlas Search `_ - which is a more powerful and flexible text search solution. - The rest of this section discusses text indexes and not Atlas Search. - -To perform text search with Mongoid, follow these steps: - -1. Define a text index on a model. -2. Create the text index on the server. -3. Build a text search query. - - -Defining Text Search Index --------------------------- - -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: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, type: String - field :description, type: String - - index description: 'text' - end - -Note that the index type (``text``) must be given as a string, not as a symbol. - - -Creating Text Index -------------------- - -To create the index, invoke the ``db:mongoid:create_indexes`` Rake task: - -.. code-block:: ruby - - bundle exec rake db:mongoid:create_indexes - - -Querying Using Text Index -------------------------- - -To find bands whose description contains "ounces" or its variations, use the -`$text operator `_: - -.. code-block:: ruby - - Band.where('$text' => {'$search' => 'ounces'}).to_a - # => [#] - -Note that the description contains the word "ounce" even though the search -query was "ounces". - -Note also that when performing text search, the name of the field is not -explicitly specified - ``$text`` operator searches all fields indexed with -the text index. diff --git a/source/reference/transactions.txt b/source/reference/transactions.txt deleted file mode 100644 index ecf2465e..00000000 --- a/source/reference/transactions.txt +++ /dev/null @@ -1,232 +0,0 @@ -.. _transactions: - -************ -Transactions -************ - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Version 4.0 of the MongoDB server introduces -`multi-document transactions `_. -(Updates to multiple fields within a single document are atomic in all -versions of MongoDB). Transactions require a non-standalone MongoDB topology -and Ruby driver version 2.6 or higher. A higher level transaction API requires -Mongoid version 9.0 or higher, while a lower level API requires Mongoid -version 6.4 or higher. - -Using Transactions -================== - -Higher Level API ----------------- - -A transaction can be started by calling the ``transaction`` method on an instance -of a Mongoid document class, on a Mongoid document class, on or ``Mongoid`` module: - -.. code-block:: ruby - - Band.transaction do - Band.create(title: 'Led Zeppelin') - end - - band = Band.create(title: 'Deep Purple') - band.transaction do - band.active = false - band.save! - end - - Mongoid.transaction do - band.destroy - end - -When the ``transaction`` method is called, Mongoid does the following: - -* creates a session on a client that is used by the receiver of the - ``transaction`` method call; -* starts a transaction on the session; -* executes the given block; -* commits the transaction if no exception raised in the block; - - * calls ``after_commit`` callbacks for all objects modified inside the transaction -* aborts the transaction if an exception is raised in the block; - - * calls ``after_rollback`` callbacks for all objects modified inside the transaction -* closes the session - -.. note:: - - Since a transaction is tied to a particular client, _only_ operations on - the same client will be in scope of the transaction. Therefore it - is recommended that only objects that use the same client are used inside the - ``transaction`` method block. - - .. code-block:: ruby - - class Author - include Mongoid::Document - store_in client: :encrypted_client - end - - class User - include Mongoid::Document - store_in client: :encrypted_client - end - - class Article - include Mongoid::Document - # This class uses the :default client - end - - # Transaction is started on the :encrypted_client - Author.transaction do - # This operation uses the same client, so it is in the transaction - Author.create! - # This operation also uses the same client, so it is in the transaction - User.create! - # This operation uses a different client, so it is NOT in the transaction - Article.create! - end - -.. note:: - When ``transaction`` method is called on ``Mongoid`` module, the transaction - is created using the ``:default`` client. - -Aborting Transaction -~~~~~~~~~~~~~~~~~~~~ - -Any exception raised inside the ``transaction`` method block aborts the -transaction. Normally the raised exception passed on, except for the -``Mongoid::Errors::Rollback``. This error should be raised if you want to -explicitly abort the transaction without passing on an exception. - -Callbacks -~~~~~~~~~ - -Transaction API introduces two new callbacks - ``after_commit`` and ``after_rollback``. - -``after_commit`` callback is triggered for an object that was created, saved, -or destroyed: - -* after transaction is committed if the object was modified inside the transaction; -* after the object was persisted if the object was modified outside a transaction. - -.. note:: - In any case ``after_commit`` callback is triggered only after all other callbacks - were executed successfully. Therefore, if the object is modified without a - transaction, it is possible that the object was persisted, but ``after_commit`` - callback was not triggered (for example, an exception raised in ``after_save`` - callback). - -``after_rollback`` callback is triggered for an object that was created, saved, -or destroyed inside a transaction if the transaction was aborted. ``after_rollback`` -is never triggered without a transaction. - - -Lower Level API ---------------- - -In order to start a transaction, the application must have a :ref:`session `. - -A transaction can be started by calling the ``start_transaction`` method on a session, which can be -obtained by calling the ``with_session`` method on either a model class or instance: - -.. code-block:: ruby - - class Person - include Mongoid::Document - end - - Person.with_session do |session| - session.start_transaction - end - - person = Person.new - person.with_session do |session| - session.start_transaction - end - -It is also possible to specify read concern, write concern and read preference -when starting a transaction: - -.. code-block:: ruby - - Person.with_session do |session| - session.start_transaction( - read_concern: {level: :majority}, - write_concern: {w: 3}, - read: {mode: :primary}) - end - -A transaction may be committed or aborted. The corresponding methods to do so are -``commit_transaction`` and ``abort_transaction``, again on the session instance: - -.. code-block:: ruby - - Person.with_session do |session| - session.commit_transaction - end - - Person.with_session do |session| - session.abort_transaction - end - -If a session ends with an open transaction, -`the transaction is aborted `_. - -The transaction commit `can be retried `_ -if it fails. Here is the Ruby code to do so: - -.. code-block:: ruby - - begin - session.commit_transaction - rescue Mongo::Error => e - if e.label?(Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL) - retry - else - raise - end - end - -Note that in order to perform operations within the transaction, operations must use the same client -that the session was initiated on. By default, all operations will be done on the default client: - -.. code-block:: ruby - - class Person - include Mongoid::Document - end - - class Post - include Mongoid::Document - end - - Person.with_session do |s| - s.start_transaction - Person.create! - Person.create! - Post.create! - s.commit_transaction - end - -To explicitly use a different client, use the ``with`` method: - -.. code-block:: ruby - - Post.with(client: :other) do - Person.with(client: :other) do - Person.with_session do |s| - s.start_transaction - Person.create! - Person.create! - Post.create! - s.commit_transaction - end - end - end diff --git a/source/reference/validation.txt b/source/reference/validation.txt deleted file mode 100644 index 4ec578ad..00000000 --- a/source/reference/validation.txt +++ /dev/null @@ -1,67 +0,0 @@ -.. _mongoid-validation: -.. _validation: - -********** -Validation -********** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Mongoid includes ``ActiveModel::Validations`` to supply the basic -validation plus an additional associated and uniqueness validator. - -See the `Active Record Validations -`_ -Rails guide and `ActiveModel::Validations -`_ -documentation for more information. - -Mongoid behaves slightly differently to Active Record when using ``#valid?`` -on already persisted data. Active Record's ``#valid?`` will run all -validations whereas Mongoid's ``#valid?`` will only run validations on -documents that are in memory as an optimization. - - -``validates_uniqueness_of`` and ``:conditions`` Option -======================================================= - -The ``:conditions`` option to ``validates_uniqueness_of`` can be used to -provide additional conditions to add to the database query looking for -identical documents. This option does not influence when the validation -is executed because it is not considered when Mongoid retrieves the present -value of the respective field from the model. Consider the following example: - -.. code-block:: ruby - - class Band - include Mongoid::Document - - field :name, type: String - field :year, type: Integer - - validates_uniqueness_of :name, conditions: -> { where(:year.gte => 2000) } - end - - # OK - Band.create!(name: "Sun Project", year: 2000) - - # Fails validation because there is a band with the "Sun Project" name - # and year 2000 in the database, even though the model being created now - # does not have a year. - Band.create!(name: "Sun Project") - - -Read preference with ``validates_uniqueness_of`` -================================================ - -In order to validate the uniqueness of an attribute, Mongoid must check that -the value for that attribute does not already exist in the database. If Mongoid -queries a secondary member of the replica set, there is a possibility that it -is reading stale data. Because of this, the queries used to check a -``validates_uniqueness_of`` validation always use read preference ``primary``. diff --git a/source/schema-configuration.txt b/source/schema-configuration.txt deleted file mode 100644 index 091c4bcf..00000000 --- a/source/schema-configuration.txt +++ /dev/null @@ -1,31 +0,0 @@ -.. _schema-configuration: - -******************** -Schema Configuration -******************** - -.. default-domain:: mongodb - -.. toctree:: - :titlesonly: - - reference/fields - reference/inheritance - reference/associations - reference/validation - reference/collection-configuration - reference/indexes - reference/sharding - -Overview --------- - -See the following sections to learn how to configure a schema with Mongoid: - -- :ref:`Field Definition ` -- :ref:`Inheritance ` -- :ref:`Associations ` -- :ref:`Validation ` -- :ref:`Collection Configuration ` -- :ref:`Index Management ` -- :ref:`Sharding Management ` \ No newline at end of file diff --git a/source/tutorials.txt b/source/tutorials.txt deleted file mode 100644 index 2a564253..00000000 --- a/source/tutorials.txt +++ /dev/null @@ -1,17 +0,0 @@ -.. _tutorials: - -********* -Tutorials -********* - -.. toctree:: - :titlesonly: - - tutorials/common-errors - -Overview --------- - -See the following sections to learn more about working with Mongoid: - -- :ref:`Common Errors ` \ No newline at end of file diff --git a/source/tutorials/common-errors.txt b/source/tutorials/common-errors.txt deleted file mode 100644 index 226e3b0e..00000000 --- a/source/tutorials/common-errors.txt +++ /dev/null @@ -1,51 +0,0 @@ -.. _common-errors: - -************* -Common Errors -************* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Mongoid/Moped Authentication Error: failed with error 13 -======================================================== - -If you are encountering the following error: - -.. code-block:: ruby - - Moped::Errors::OperationFailure: The operation: #1, :w=>1} - @fields=nil> - failed with error 13: "not authorized for insert on mongose_development.people" - - See https://github.com/mongodb/mongo/blob/master/docs/errors.md - for details about this error. - from /.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/bundler/gems/moped-10abbf3eac37/lib/moped/operation/read.rb:50:in `block in execute' - from /.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/bundler/gems/moped-10abbf3eac37/lib/moped/node.rb:594:in `[]' - from /.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/bundler/gems/moped-10abbf3eac37/lib/moped/node.rb:594:in `block (2 levels) in flush' - from /.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/bundler/gems/moped-10abbf3eac37/lib/moped/node.rb:593:in `map' - from /.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/bundler/gems/moped-10abbf3eac37/lib/moped/node.rb:593:in `block in flush' - from /.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/bundler/gems/moped-10abbf3eac37/lib/moped/node.rb:617:in `block in logging' - from /.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/activesupport-4.2.0/lib/active_support/notifications.rb:164:in `block in instrument' - from /.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/activesupport-4.2.0/lib/active_support/notifications/instrumenter.rb:20:in `instrumen - -This error is caused by Moped, a Ruby driver that is no longer in use by -Mongoid. Upgrading to Mongoid 5+ should fix this issue. - -You can find more information about this issue here: -`MONGOID-4067 `_. - diff --git a/source/whats-new.txt b/source/whats-new.txt index e6e52baa..fda72e88 100644 --- a/source/whats-new.txt +++ b/source/whats-new.txt @@ -116,7 +116,7 @@ The deprecated class ``{+odm+}::Errors::InvalidStorageParent`` has been removed. ``around_*`` Callbacks for Embedded Documents are Ignored ---------------------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {+odm+} v8.x and older allow user to define ``around_*`` callbacks for embedded documents. Starting in v9.0, these callbacks are ignored and will not be executed. @@ -142,7 +142,7 @@ Removal of Deprecated Options **Breaking change:** The following config options are removed in {+odm+} v9.0. Please ensure you have removed all references to these from your app. -If you were using ``config.load_defaults 8.1`` prior to upgrading, you will not +If you were using ``config.load_defaults 8.1`` before upgrading, you will not experience any behavior change. Refer to earlier release notes for the meaning of each option. @@ -205,7 +205,7 @@ the ``touch`` method. band.changes # => {} Sandbox Mode for Rails Console ------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {+odm+} now supports Rails console sandbox mode. If the Rails console started with ``--sandbox`` flag, {+odm+} starts a transaction on the ``:default`` client @@ -545,7 +545,7 @@ Consider the following code: record = Model.with(collection: 'other_collection') { Model.first } record.update(field: 'value') -Prior to {+odm+} v9.0, this could would silently fail to execute the update, +Before {+odm+} v9.0, the preceding code silently fails to execute the update, because the storage options (here, the specification of an alternate collection for the model) would not be remembered by the record. Thus, the record would be loaded from ``other_collection``, but when updated, would attempt From 94f6a97b169536a996c20aa1915849a6ce9f91be Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:35:18 -0500 Subject: [PATCH 107/113] DOCSP-46555: rails integration (#92) * wip * DOCSP-46555: rails integration * RM PR fixes 1 --- source/index.txt | 4 +- source/integrations-tools.txt | 21 +++ .../{ => integrations-tools}/add-existing.txt | 2 + .../integrations-tools/rails-integration.txt | 136 ++++++++++++++++++ source/legacy-files/rails-integration.txt | 100 ------------- 5 files changed, 161 insertions(+), 102 deletions(-) create mode 100644 source/integrations-tools.txt rename source/{ => integrations-tools}/add-existing.txt (99%) create mode 100644 source/integrations-tools/rails-integration.txt delete mode 100644 source/legacy-files/rails-integration.txt diff --git a/source/index.txt b/source/index.txt index cca00495..4a4cfe64 100644 --- a/source/index.txt +++ b/source/index.txt @@ -14,14 +14,14 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Quick Start - {+ror+} Quick Start - Sinatra - Add {+odm+} to an Existing Application Configuration Interact with Data Model Your Data Secure Your Data + Integrations & Tools API Documentation What's New Compatibility Issues & Help /additional-resources - /ecosystem \ No newline at end of file + /ecosystem diff --git a/source/integrations-tools.txt b/source/integrations-tools.txt new file mode 100644 index 00000000..6def4a4c --- /dev/null +++ b/source/integrations-tools.txt @@ -0,0 +1,21 @@ +.. _mongoid-integrations-tools: + +==================== +Integrations & Tools +==================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: ruby framework, odm, rails, sinatra, ecosystem + +.. toctree:: + :caption: Integrations & Tools + + Add {+odm+} to an Existing Application + Rails Integration + +.. TODO +.. External Libraries diff --git a/source/add-existing.txt b/source/integrations-tools/add-existing.txt similarity index 99% rename from source/add-existing.txt rename to source/integrations-tools/add-existing.txt index 6fa94c9d..21c5c15a 100644 --- a/source/add-existing.txt +++ b/source/integrations-tools/add-existing.txt @@ -46,6 +46,8 @@ the following steps: #. Create {+odm+} models to interact with your data. +.. _mongoid-add-existing-rails: + Rails Application ----------------- diff --git a/source/integrations-tools/rails-integration.txt b/source/integrations-tools/rails-integration.txt new file mode 100644 index 00000000..ff703625 --- /dev/null +++ b/source/integrations-tools/rails-integration.txt @@ -0,0 +1,136 @@ +.. _mongoid-rails-integration: + +================= +Rails Integration +================= + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: web framework, api, code example, ruby + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn about features that are automatically +enabled when you use {+odm+} in a {+ror+} application. This guide also +describes Rails-related functionality that you can enable in your application. + +Configuration +------------- + +You can configure {+odm+}-specific options and other Rails-environment +specific options in your main application file by accessing +``config.mongoid``. The ``mongoid:config`` generator creates an +initializer in the ``config/initializers/mongoid.rb`` file. + +.. note:: + + Any options set in your ``config/mongoid.yml`` file + take precedence over options set elsewhere. For this reason, use + ``mongoid.yml`` as the default location for {+odm+} configuration + when possible. + +.. TODO To learn more about available configuration options, + see the :ref:`` section. + +The following code demonstrates how to create a Rails logger by +accessing ``config.mongoid``: + +.. code-block:: ruby + + module MyApplication + class Application < Rails::Application + config.mongoid.logger = Logger.new(STDERR, :warn) + end + end + +.. TODO To learn more about logging settings, see the :ref:`` guide. + +Model Preloading +---------------- + +To set up single collection inheritance, {+odm+} must preload all +models before every request in development mode. This can slow down your +application, so if you are not using any inheritance you can turn this +feature off. + +The following code demonstrates how you can turn off preloading by +setting the ``preload_models`` feature to ``false``: + +.. code-block:: ruby + + config.mongoid.preload_models = false + +Exceptions +---------- + +Similar to Active Record, {+odm+} configures Rails to automatically +convert certain exceptions to HTTP status codes. The following list +provides the conversions between {+odm+} exceptions and HTTP codes: + +- ``Mongoid::Errors::DocumentNotFound``: Converted to ``404 Not Found`` +- ``Mongoid::Errors::Validations``: Converted to ``422 Unprocessable Content`` + +Execution Time Logging +---------------------- + +{+odm+} can output the time spent executing database commands to the Rails +instrumentation event ``process_action.action_controller``. {+odm+} +obtains these values through driver command monitoring. You application +logs this time amount with view time as shown in the following output: + +.. code-block:: none + + Completed 200 OK in 2739ms (Views: 12.6ms | MongoDB: 0.2ms) + +This logging is set up automatically in your Rails application. + +.. note:: Time Calculation + + The time indicated in log entries is the time that the MongoDB + deployment takes to run MongoDB operations in addition to the time taken to + send commands and receive results from the MongoDB Server. It does + not include time taken by the driver and {+odm+} to generate the + queries, cast types, or otherwise process the results. + +Rake Tasks +---------- + +You can use following rake tasks for {+odm+} when using the Rails +framework: + +- ``db:create_indexes``: Reads all index definitions from the models and + attempts to create them in the database +- ``db:remove_indexes``: Removes indexes for each model +- ``db:drop``: Drops all collections in the database except for system + collections +- ``db:purge``: Deletes all data, including indexes, from the database +- ``db:seed``: Seeds the database from the ``db/seeds.rb`` file +- ``db:setup``: Creates indexes and seeds the database + +The following rake tasks exist only for framework dependency purposes +and do not perform any actions: + +- ``db:test:prepare`` +- ``db:schema:load`` +- ``db:create`` +- ``db:migrate`` + +Additional Information +---------------------- + +To learn about how to set up a new Rails application that uses {+odm+}, +see the :ref:`mongoid-quick-start-rails` guide. + +To learn how to add {+odm+} to an existing Rails application, see the +:ref:`mongoid-add-existing-rails` section of the Add {+odm+} to an +Existing Application guide. diff --git a/source/legacy-files/rails-integration.txt b/source/legacy-files/rails-integration.txt deleted file mode 100644 index 15ea483a..00000000 --- a/source/legacy-files/rails-integration.txt +++ /dev/null @@ -1,100 +0,0 @@ -.. _rails-integration: - -***************** -Rails Integration -***************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Mongoid seamlessly integrates into {+ror+} applications. -This page describes features that are automatically enabled in the context -of a Rails application and Rails-related functionality which can be -manually enabled. - - -Configuration -============= - -You can set Mongoid configuration options in your ``application.rb`` along with -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 precedence over options set elsewhere; it is recommended that whenever -possible you use ``mongoid.yml`` as the default location for Mongoid -configuration. - -.. code-block:: ruby - - module MyApplication - class Application < Rails::Application - config.mongoid.logger = Logger.new(STDERR, :warn) - end - end - - -Model Preloading -================ - -In order to properly set up single collection inheritance, Mongoid needs to preload all -models before every request in development mode. This can get slow, so if you are not -using any inheritance it is recommended you turn this feature off. - -.. code-block:: ruby - - config.mongoid.preload_models = false - - -Exceptions -========== - -Similarly to ActiveRecord, Mongoid configures Rails to automatically convert -certain exceptions to well-known HTTP status codes, as follows: - -.. code-block:: ruby - - Mongoid::Errors::DocumentNotFound : 404 - Mongoid::Errors::Validations : 422 - - -Controller Runtime Instrumentation -================================== - -Mongoid provides time spent executing MongoDB commands (obtained via a -driver command monitoring subscription) to Rails' instrumentation event -``process_action.action_controller``. This time is logged together with view -time like so: - -.. code-block:: none - - Completed 200 OK in 2739ms (Views: 12.6ms | MongoDB: 0.2ms) - -This logging is set up automatically. - -Note: the time indicated is the time taken by MongoDB cluster to execute -MongoDB operations, plus the time taken to send commands and receive -results from MongoDB over the network. It does not include time taken by -the driver and Mongoid to generate the queries or type cast and otherwise -process the results. - -Rake Tasks -========== - -Mongoid provides the following rake tasks when used in a Rails environment: - -- ``db:create``: Exists only for dependency purposes, does not actually do anything. -- ``db:create_indexes``: Reads all index definitions from the models and attempts to create them in the database. -- ``db:remove_indexes``: Reads all secondary index definitions from the models. -- ``db:drop``: Drops all collections in the database with the exception of the system collections. -- ``db:migrate``: Exists only for dependency purposes, does not actually do anything. -- ``db:purge``: Deletes all data, including indexes, from the database. Since 3.1.0 -- ``db:schema:load``: Exists only for framework dependency purposes, does not actually do anything. -- ``db:seed``: Seeds the database from db/seeds.rb -- ``db:setup``: Creates indexes and seeds the database. -- ``db:test:prepare``: Exists only for framework dependency purposes, does not actually do anything. From eabedbaafcdf87eac4f3a9f4200694817dcc3381 Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:56:42 -0800 Subject: [PATCH 108/113] DOCSP-45359 External Resources (#94) * add additional resources page * edits * feedback --- source/additional-resources.txt | 61 --------- source/ecosystem.txt | 84 ------------ source/integrations-tools.txt | 4 +- .../integrations-tools/external-resources.txt | 127 ++++++++++++++++++ 4 files changed, 128 insertions(+), 148 deletions(-) delete mode 100644 source/additional-resources.txt delete mode 100644 source/ecosystem.txt create mode 100644 source/integrations-tools/external-resources.txt diff --git a/source/additional-resources.txt b/source/additional-resources.txt deleted file mode 100644 index 065b8ee2..00000000 --- a/source/additional-resources.txt +++ /dev/null @@ -1,61 +0,0 @@ -******************** -Additional Resources -******************** - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -This page lists some of the third-party guides and blog posts about Mongoid, -as well as sample Mongoid applications. Additional resources for the driver -are listed on the `respective driver page -`_. - - -Screencasts -=========== - -- `RailsCasts: Mongoid (revised) - `_ - - An overview of Mongoid, by Ryan Bates including the basics - of setting up an app, querying for documents, adding embedded - associations, overriding the id, and more. - -- `{+ror+} Web Services and Integration with MongoDB, Week 3: Mongoid - `_ - - A detailed introduction to Mongoid and {+ror+} web services. - -- `Create a search bar in Rails with Mongoid - `_ - - A Tutorial explaining how to implement text search with Mongoid. - - -Articles -======== - -- `A Simple Content Management System in Sinatra `_ - - Building a content management application with Sinatra and Mongoid. - -- `How To Create A Ruby API With Sinatra `_ - - Creating a Sinatra API with Mongoid. - -- `Converting an existing {+ror+} application to MongoDB `_ - - How to Convert an existing {+ror+} application to use MongoDB and Mongoid. - - -Sample Applications -=================== - -- `Mongoid Demo `_ - - A repository containing sample applications using Mongoid. diff --git a/source/ecosystem.txt b/source/ecosystem.txt deleted file mode 100644 index 9d89cc70..00000000 --- a/source/ecosystem.txt +++ /dev/null @@ -1,84 +0,0 @@ -********* -Ecosystem -********* - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Mongoid has an extensive ecosystem of libraries integrating with or built -on top of Mongoid, are listed below. - - -Projects -======== - -- `Workarea Commerce `_ - - Workarea is an enterprise-grade {+ror+} commerce platform that uses Mongoid. - - -Extension Libraries -=================== - -- `Mongoid Tree `_ - - A tree structure for Mongoid documents using the materialized path pattern. - -- `Mongoid Token `_ - - A little random, unique token generator for Mongoid documents. - -- `Mongoid Collection Snapshot `_ - - Easy maintenance of collections of processed data in MongoDB with the Mongoid ODM. - -- `Mongoid Locker `_ - - Document-level locking for MongoDB via Mongoid. - -- `Mongo Beautiful Logger `_ - - A simple and beautiful logger library for MongoDB in your Ruby/Rails app. - -- `Mongoid Search `_ - - Simple full text search for Mongoid. - -- `Mongoid Fulltext Search `_ - - Full-text search using n-gram matching for the Mongoid ODM. - - -Integration Libraries -===================== - -- `CarrierWave Mongoid `_ - - Mongoid Support for the Carrierwave file uploads library. - -- `Mongoid RSpec `_ - - RSpec matchers and macros for Mongoid applications. - -- `RailsAdmin `_ supports Mongoid out - of the box. - -- `ActiveAdmin Mongoid `_ - - ActiveAdmin hacks to support Mongoid. - -- `Mongoid History `_ - - Multi-user non-linear history tracking, auditing, undo, redo for mongoid. -- `Delayed Job Mongoid `_ - - Mongoid backend for delayed_job. - -- `Mongo Session Store `_ - - A Rails-compatible session store for Mongoid. diff --git a/source/integrations-tools.txt b/source/integrations-tools.txt index 6def4a4c..0e5110db 100644 --- a/source/integrations-tools.txt +++ b/source/integrations-tools.txt @@ -16,6 +16,4 @@ Integrations & Tools Add {+odm+} to an Existing Application Rails Integration - -.. TODO -.. External Libraries + External Resources diff --git a/source/integrations-tools/external-resources.txt b/source/integrations-tools/external-resources.txt new file mode 100644 index 00000000..20994095 --- /dev/null +++ b/source/integrations-tools/external-resources.txt @@ -0,0 +1,127 @@ +.. _mongoid-external-resources: + +================== +External Resources +================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: mongoid, ruby, libraries, ecosystem, tools + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can find external tools and resources that you can use to +enhance your {+odm+} applications. These resources include external projects and +libraries, and various learning resources. + +Projects and Libraries +---------------------- + +The following sections describe projects and libraries that are integrated with +or built on top of {+odm+}. + +Projects +~~~~~~~~ + +- `Workarea Commerce `__ is an + enterprise-grade {+ror+} commerce platform that uses {+odm+}. + +Extension Libraries +~~~~~~~~~~~~~~~~~~~ + +- `Mongoid Tree `__ is a tree structure + for {+odm+} documents that uses the materialized path pattern. + +- `Mongoid Token `__ generates random, + tokens for {+odm+} documents. + +- `Mongoid Collection Snapshot + `__ helps maintain + collections of processed data in {+odm+} applications. + +- `Mongoid Locker `__ provides + document-level locking for {+odm+} applications. + +- `Mongo Beautiful Logger + `__ is a library that + formats your MongoDB logs. + +- `Mongoid Search `__ provides + full-text search for {+odm+}. + +- `Mongoid Fulltext Search `__ + provides full-text search using n-gram matching for {+odm+}. + +Integration Libraries +~~~~~~~~~~~~~~~~~~~~~ + +- `CarrierWave Mongoid + `__ provides + {+odm+} support for the Carrierwave file-uploads library. + +- `Mongoid RSpec `__ provides RSpec + matchers and macros for {+odm+} applications. + +- `RailsAdmin `__ is a Rails engine that + provides an interface for managing your data. + +- `ActiveAdmin Mongoid `__ + provides ActiveAdmin hacks to support {+odm+}. + +- `Mongoid History `__ is a + multi-user, non-linear history tracker for {+odm+}. + +- `Delayed Job Mongoid + `__ is a {+odm+} backend for + ``delayed_job``. + +- `Mongo Session Store `__ is a + Rails-compatible session store for {+odm+}. + +Learning Resources +------------------ + +The following sections provide screencasts, articles, and sample +applications that you can use to learn more about {+odm+}. + +Screencasts +~~~~~~~~~~~ + +- `RailsCasts: Mongoid (revised) with Ryan Bates + `__ gives an overview of {+odm+}. + It includes the basics of setting up an application and working with data. + +- `Create a search bar in Rails with Mongoid + `__ is a tutorial that explains how + to implement text search with {+odm+}. + +Articles +~~~~~~~~ + +- `A Simple Content Management System in Sinatra + `__: + Building a content management application with Sinatra and {+odm+}. + +- `How To Create A Ruby API With Sinatra + `__: Creating a + Sinatra API with {+odm+}. + +- `Converting an existing {+ror+} application to MongoDB + `__: Learn how to convert an + existing {+ror+} application to use MongoDB and {+odm+}. + +Sample Applications +~~~~~~~~~~~~~~~~~~~ + +- `Mongoid Demo `__: A repository + containing sample applications that use {+odm+}. \ No newline at end of file From 218d8ed90ca0b152c63f7b976850931e0dc75590 Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Fri, 24 Jan 2025 08:11:44 -0800 Subject: [PATCH 109/113] DOCSP-42743 Collection config (#95) --- source/configuration.txt | 6 +- source/configuration/collection-config.txt | 115 ++++++++++++++++++ .../configuration/collection-config.rb | 39 ++++++ 3 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 source/configuration/collection-config.txt create mode 100644 source/includes/configuration/collection-config.rb diff --git a/source/configuration.txt b/source/configuration.txt index 1c486bca..aa20b4a1 100644 --- a/source/configuration.txt +++ b/source/configuration.txt @@ -20,8 +20,7 @@ Configuration Logging Query Cache Middleware Forking Servers - -.. Collection Configuration + Collection Configuration In this section, you can learn how to configure different options with {+odm+}. @@ -42,3 +41,6 @@ In this section, you can learn how to configure different options with {+odm+}. - :ref:`mongoid-forking-server-config`: Learn how to configure your application to use a forking web server. + +- :ref:`mongoid-collection-config`: Learn how to specify configuration options + for a MongoDB collection in your application. diff --git a/source/configuration/collection-config.txt b/source/configuration/collection-config.txt new file mode 100644 index 00000000..9d2c9e22 --- /dev/null +++ b/source/configuration/collection-config.txt @@ -0,0 +1,115 @@ +.. _mongoid-collection-config: + +======================== +Collection Configuration +======================== + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: code example, collections, time series, capped collection + +.. contents:: On this page + :local: + :backlinks: none + :depth: 2 + :class: singlecol + +Overview +-------- + +In this guide, you can learn how to specify configuration options for a +collection in your {+odm+} application. + +Configure Collection Options +---------------------------- + +You can specify configuration options for a collection by using the +``:collection_options`` argument with the ``store_in`` +macro. The ``:collection_options`` argument accepts any collection option that +your {+ruby-driver+} and MongoDB server version supports. + +.. note:: + + You must explicitly create a collection to apply any specified collection + options. Create your collection by running the collection management Rake task, as + shown in :ref:`mongoid-create-collection-rake` section of this guide. + +To learn more about collection options available in the {+ruby-driver+}, see the +:ruby:`Collections ` guide in the {+ruby-driver+} +documentation. + +The following sections show examples of how to configure collection options when +using {+odm+}. + +Time Series Collection +~~~~~~~~~~~~~~~~~~~~~~ + +Time series collections efficiently store sequences of measurements over a +period of time. The following example shows how to configure a time series +collection: + +.. literalinclude:: /includes/configuration/collection-config.rb + :language: ruby + :start-after: # start-time-series-config + :end-before: # end-time-series-config + :emphasize-lines: 7-13 + +To learn more about time series collections, see the :manual:`Time Series Collections +` guide in the MongoDB {+server-manual+}. + +Capped Collection +~~~~~~~~~~~~~~~~~ + +Capped collections have maximum size or document counts that prevent them from +growing beyond a specified threshold. The following example shows how to configure +a capped collection: + +.. literalinclude:: /includes/configuration/collection-config.rb + :language: ruby + :start-after: # start-capped-collection-config + :end-before: # end-capped-collection-config + :emphasize-lines: 4-7 + +To learn more about capped collections, see the :manual:`Capped Collections +` guide in the MongoDB {+server-manual+}. + +Default Collation +~~~~~~~~~~~~~~~~~ + +Collations are sets of rules for how to compare strings, typically in a +particular natural language. The following example shows how to specify a +default collation to use on a +collection: + +.. literalinclude:: /includes/configuration/collection-config.rb + :language: ruby + :start-after: # start-default-collation-config + :end-before: # end-default-collation-config + :emphasize-lines: 4-8 + +To learn more about collations, see the :manual:`Collation +` guide in the MongoDB {+server-manual+}. + +.. _mongoid-create-collection-rake: + +Collection Management Rake Task +------------------------------- + +To apply the collection options you specify in your {+odm+} application, you +must explicitly create the corresponding collection. To do so, use the +``db:mongoid:create_collections`` Rake task by running the following command in +your shell: + +.. code-block:: bash + + rake db:mongoid:create_collections + +You can also run the ``create_collection`` command on a single model in the +Rails console, as shown in the following example: + +.. code-block:: ruby + + Model.create_collection diff --git a/source/includes/configuration/collection-config.rb b/source/includes/configuration/collection-config.rb new file mode 100644 index 00000000..fc687d66 --- /dev/null +++ b/source/includes/configuration/collection-config.rb @@ -0,0 +1,39 @@ +# start-time-series-config +class Measurement + include Mongoid::Document + + field :temperature, type: Integer + field :timestamp, type: Time + + store_in collection_options: { + time_series: { + timeField: "timestamp", + granularity: "minutes" + }, + expire_after: 604800 + } +end +# end-time-series-config + +# start-capped-collection-config +class Blog + include Mongoid::Document + + store_in collection_options: { + capped: true, + size: 1024 + } +end +# end-capped-collection-config + +# start-default-collation-config +class Title + include Mongoid::Document + + store_in collection_options: { + collation: { + locale: 'fr' + } + } +end +# end-default-collation-config \ No newline at end of file From aea5b975767956de3504fb5064d88ae18f9b22c5 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Fri, 24 Jan 2025 16:41:58 -0500 Subject: [PATCH 110/113] DOCSP-42730: landing page (#96) * DOCSP-42730: landing page * MW PR fixes 1 * small fix * small fix * small fix --- snooty.toml | 4 +- source/index.txt | 83 ++++++++++++-- source/integrations-tools.txt | 4 + source/interact-data.txt | 4 + source/{ => interact-data}/aggregation.txt | 0 .../legacy-files/collection-configuration.txt | 107 ------------------ source/security.txt | 3 +- 7 files changed, 86 insertions(+), 119 deletions(-) rename source/{ => interact-data}/aggregation.txt (100%) delete mode 100644 source/legacy-files/collection-configuration.txt diff --git a/snooty.toml b/snooty.toml index 02e25f34..35af2fcd 100644 --- a/snooty.toml +++ b/snooty.toml @@ -12,10 +12,7 @@ sharedinclude_root = "https://raw.githubusercontent.com/10gen/docs-shared/main/" toc_landing_pages = [ "/quick-start-rails", "/quick-start-sinatra", - "/interact-data", "/interact-data/specify-query", - "/data-modeling", - "/configuration", "/issues-and-help" ] @@ -35,3 +32,4 @@ api = "https://www.mongodb.com/docs/mongoid/current/api" ruby-api = "https://www.mongodb.com/docs/ruby-driver/current/api" active-record-docs = "https://guides.rubyonrails.org" shared-library = "Automatic Encryption Shared Library" +mdb-server = "MongoDB Server" diff --git a/source/index.txt b/source/index.txt index 4a4cfe64..3ed8ed5d 100644 --- a/source/index.txt +++ b/source/index.txt @@ -1,14 +1,10 @@ +.. _mongoid-odm-landing: .. _mongoid-odm: ======= {+odm+} ======= -{+odm+} is the officially supported object-document mapper (ODM) for -MongoDB in Ruby. To work with {+odm+} from the command line using -``rails``-like tooling, you can use the `railsmdb -`_ utility. - .. toctree:: :titlesonly: @@ -20,8 +16,79 @@ MongoDB in Ruby. To work with {+odm+} from the command line using Secure Your Data Integrations & Tools API Documentation - What's New Compatibility + What's New Issues & Help - /additional-resources - /ecosystem + View the Source + +Introduction +------------ + +Welcome to the documentation site for {+odm+}. {+odm+} is the officially +supported object-document mapper (ODM) for MongoDB in {+language+}. By +using {+odm+}, you can easily interact with your data and create +flexible data models native to {+language+} applications. + +You can add {+odm+} to your {+language+} application to connect it to +a MongoDB database. Install {+odm+} by adding it to your project's +``Gemfile`` or set up a runnable project by following one of the +Quick Start guides. + +Quick Start +----------- + +Learn how to establish a connection to MongoDB Atlas and begin +working with data by following one of the following guides: + +- :ref:`mongoid-quick-start-rails` +- :ref:`mongoid-quick-start-sinatra` + +Configuration +------------- + +To learn how to configure different options in your {+odm+} application, +see the :ref:`mongoid-configuration` section. + +Interact with Data +------------------ + +To learn how to use {+odm+} to interact with your MongoDB data, +see the :ref:`mongoid-interact-data` section. + +Model Your Data +--------------- + +To learn how to model your MongoDB data as {+odm+} models, +see the :ref:`mongoid-data-modeling` section. + +Secure Your Data +---------------- + +To learn how to secure your data by using encryption, +see the :ref:`mongoid-security` section. + +Integrations & Tools +-------------------- + +To learn how to add {+odm+} to an existing application, +see the :ref:`mongoid-integrations-tools` section. This section also +includes information about the {+ror+} framework and other resources. + +Compatibility +------------- + +To learn about the versions of the {+mdb-server+}, the {+language+} +language, the {+ruby-driver+}, and {+ror+} framework that are compatible +with each version of {+odm+}, see :ref:`mongoid-compatibility`. + +What's New +---------- + +To view a list of new features and changes in each version, see the +:ref:`mongoid-whats-new` section. + +Issues & Help +------------- + +To find resources for troubleshooting and to learn about contributing to +{+odm+}, see :ref:`mongoid-issues-and-help`. diff --git a/source/integrations-tools.txt b/source/integrations-tools.txt index 0e5110db..827cdb3c 100644 --- a/source/integrations-tools.txt +++ b/source/integrations-tools.txt @@ -17,3 +17,7 @@ Integrations & Tools Add {+odm+} to an Existing Application Rails Integration External Resources + +- :ref:`mongoid-add-to-existing` +- :ref:`mongoid-rails-integration` +- :ref:`mongoid-external-resources` diff --git a/source/interact-data.txt b/source/interact-data.txt index f211ae5d..3ab217ab 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -17,6 +17,7 @@ Interact with Data Perform Data Operations Specify a Query Modify Query Results + Aggregation Search Text Transactions and Sessions Nested Attributes @@ -33,6 +34,9 @@ MongoDB data. - :ref:`mongoid-data-modify-results`: Learn how to modify the way that {+odm+} returns results from queries. +- :ref:`mongoid-aggregation`: Learn how to transform your data by using + MongoDB aggregation. + - :ref:`mongoid-data-text-search`: Learn how to perform efficient searches on text fields. diff --git a/source/aggregation.txt b/source/interact-data/aggregation.txt similarity index 100% rename from source/aggregation.txt rename to source/interact-data/aggregation.txt diff --git a/source/legacy-files/collection-configuration.txt b/source/legacy-files/collection-configuration.txt deleted file mode 100644 index 26bea016..00000000 --- a/source/legacy-files/collection-configuration.txt +++ /dev/null @@ -1,107 +0,0 @@ -.. _collection_configuration: - -************************ -Collection Configuration -************************ - -.. default-domain:: mongodb - -.. contents:: On this page - :local: - :backlinks: none - :depth: 2 - :class: singlecol - -Configuring a Document Collection -================================= - -You can specify collection options for documents using the ``store_in`` macro. -This macro accepts ``:collection_options`` argument, which can contain any collection -options that are supported by the driver. - -.. note:: - - In order to apply the options, the collection must be explicitly created up-front. - This should be done using :ref:`Collection Management Rake Task`. - -Please refer to `the driver collections page -`_ -for the more information about collection options. - -.. note:: - - Collection options depend on the driver version and MongoDB server version. - It is possible that some options, like time series collections, are not available - on older server versions. - -Time Series Collection ----------------------- - -.. code-block:: ruby - - class Measurement - include Mongoid::Document - - field :temperature, type: Integer - field :timestamp, type: Time - - store_in collection_options: { - time_series: { - timeField: "timestamp", - granularity: "minutes" - }, - expire_after: 604800 - } - end - - - -Capped Collections ------------------- - -.. code-block:: ruby - - class Name - include Mongoid::Document - - store_in collection_options: { - capped: true, - size: 1024 - } - end - -Set a Default Collation on a Collection ---------------------------------------- - -.. code-block:: ruby - - class Name - include Mongoid::Document - - store_in collection_options: { - collation: { - locale: 'fr' - } - } - end - -.. _collection-management-task: - -Collection Management Rake Task -=============================== - -If you specify collection options for a document, then the corresponding collection -must be explicitly created prior to use. To do so, use the provided -``db:mongoid:create_collections`` Rake task: - -.. code-block:: bash - - $ rake db:mongoid:create_collections - -The create collections command also works for just one model by running -in Rails console: - -.. code-block:: ruby - - # Create collection for Model - Model.create_collection diff --git a/source/security.txt b/source/security.txt index 3b8db0b6..9e2470f2 100644 --- a/source/security.txt +++ b/source/security.txt @@ -18,4 +18,5 @@ Secure Your Data In this section, you can learn how to secure your data when using {+odm+}. -- :ref:`Client-Side Field Level Encryption ` Learn how to encrypt your data with {+odm+}. \ No newline at end of file +- :ref:`Client-Side Field Level Encryption ` Learn + how to encrypt your data with {+odm+}. From ae1b9782ec97069c7502eb38b284a46531196827 Mon Sep 17 00:00:00 2001 From: Rea Rustagi <85902999+rustagir@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:39:39 -0500 Subject: [PATCH 111/113] DOCSP-46121: cleanup (#97) * cleanup * copy compat action * redirects * MW PR fixes 1 --- .../workflows/copy-compat-to-docs-shared.yml | 80 ++++++++ config/redirects | 72 ++++++- source/api.txt | 2 +- source/code-documentation.txt | 5 +- source/compatibility.txt | 2 +- source/configuration/app-config.txt | 10 +- source/configuration/collection-config.txt | 2 +- source/configuration/persistence-config.txt | 16 +- source/configuration/query-cache-config.txt | 2 + source/configuration/sharding.txt | 1 - source/data-modeling/associations.txt | 43 +++-- source/data-modeling/documents.txt | 5 +- source/data-modeling/field-behaviors.txt | 16 +- source/data-modeling/field-types.txt | 80 ++++---- source/data-modeling/indexes.txt | 1 + source/data-modeling/inheritance.txt | 15 +- source/data-modeling/validation.txt | 24 +-- .../includes/data-modeling/field-behaviors.rb | 54 +++--- source/includes/data-modeling/indexes.rb | 100 +++++----- source/includes/data-modeling/nested_attr.rb | 14 +- source/includes/interact-data/scoping.rb | 82 ++++---- source/includes/interact-data/transaction.rb | 20 +- source/includes/security/encryption.rb | 10 +- source/integrations-tools/add-existing.txt | 4 +- .../integrations-tools/external-resources.txt | 46 ++--- .../integrations-tools/rails-integration.txt | 9 +- source/interact-data/aggregation.txt | 24 +-- source/interact-data/crud.txt | 3 + source/interact-data/modify-results.txt | 48 ++--- source/interact-data/nested-attributes.txt | 16 +- source/interact-data/query-async.txt | 6 +- source/interact-data/query-cache.txt | 4 +- source/interact-data/query-persistence.txt | 3 +- source/interact-data/scoping.txt | 18 +- source/interact-data/specify-query.txt | 181 +++++++++--------- source/interact-data/text-search.txt | 8 +- source/interact-data/transaction.txt | 38 ++-- source/legacy-files/upgrading.txt | 2 +- source/quick-start-rails.txt | 5 +- .../download-and-install.txt | 4 +- source/quick-start-rails/next-steps.txt | 11 +- source/quick-start-rails/view-data.txt | 2 +- source/quick-start-sinatra.txt | 2 +- .../download-and-install.txt | 4 +- source/quick-start-sinatra/next-steps.txt | 11 +- source/quick-start-sinatra/view-data.txt | 2 +- source/security/encryption.txt | 7 +- 47 files changed, 643 insertions(+), 471 deletions(-) create mode 100644 .github/workflows/copy-compat-to-docs-shared.yml diff --git a/.github/workflows/copy-compat-to-docs-shared.yml b/.github/workflows/copy-compat-to-docs-shared.yml new file mode 100644 index 00000000..f04fa143 --- /dev/null +++ b/.github/workflows/copy-compat-to-docs-shared.yml @@ -0,0 +1,80 @@ +name: Copy Files to docs-shared + +on: + workflow_dispatch: {} # use to manually trigger workflow + push: + branches: + - "master" + paths: + - "source/includes/mongodb-compatibility-table-mongoid.rst" + - "source/includes/language-compatibility-table-mongoid.rst" + - "source/includes/rails-compatibility-table-mongoid.rst" + - "source/includes/ror-compatibility-table-mongoid.rst" + - "source/includes/ruby-driver-compatibility-table-mongoid.rst" + +jobs: + copy-file: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Copy mongodb-compat table + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + with: + source_file: "source/includes/mongodb-compatibility-table-mongoid.rst" + destination_repo: "10gen/docs-shared" + destination_folder: "dbx" + user_email: "docs-builder-bot@mongodb.com" + user_name: "docs-builder-bot" + commit_message: "Auto-import from docs-mongoid" + + - name: Copy language-compat table + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + with: + source_file: "source/includes/language-compatibility-table-mongoid.rst" + destination_repo: "10gen/docs-shared" + destination_folder: "dbx" + user_email: "docs-builder-bot@mongodb.com" + user_name: "docs-builder-bot" + commit_message: "Auto-import from docs-mongoid" + + - name: Copy ruby-on-rails-compat table + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + with: + source_file: "source/includes/rails-compatibility-table-mongoid.rst" + destination_repo: "10gen/docs-shared" + destination_folder: "dbx" + user_email: "docs-builder-bot@mongodb.com" + user_name: "docs-builder-bot" + commit_message: "Auto-import from docs-mongoid" + + - name: Copy rails-feature-compat table + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + with: + source_file: "source/includes/ror-compatibility-table-mongoid.rst" + destination_repo: "10gen/docs-shared" + destination_folder: "dbx" + user_email: "docs-builder-bot@mongodb.com" + user_name: "docs-builder-bot" + commit_message: "Auto-import from docs-mongoid" + + - name: Copy ruby-driver-compat table + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} + with: + source_file: "source/includes/ruby-driver-compatibility-table-mongoid.rst" + destination_repo: "10gen/docs-shared" + destination_folder: "dbx" + user_email: "docs-builder-bot@mongodb.com" + user_name: "docs-builder-bot" + commit_message: "Auto-import from docs-mongoid" diff --git a/config/redirects b/config/redirects index 2516d155..a0b5418b 100644 --- a/config/redirects +++ b/config/redirects @@ -1,10 +1,72 @@ define: prefix docs/mongoid -define: base https://www.mongodb.com/docs/mongoid -define: versions 8.1 upcoming +define: base https://www.mongodb.com/${prefix} +define: versions v9.0 master -symlink: current -> 8.1 +symlink: current -> v9.0 +symlink: upcoming -> master raw: ${prefix}/ -> ${base}/current/ -raw: ${prefix}/master -> ${base}/upcoming/ +raw: ${prefix}/stable -> ${base}/current/ -raw: ${prefix}/current/release-notes/mongoid-7.0 -> ${base}/current/ +# Redirects for standardized (new) page URLs in old docs +[*-v9.0]: ${prefix}/${version}/compatibility/ -> ${base}/${version}/reference/compatibility/ +[*-v9.0]: ${prefix}/${version}/configuration/ -> ${base}/${version}/reference/configuration/ +[*-v9.0]: ${prefix}/${version}/integrations-tools/rails-integration/ -> ${base}/${version}/reference/rails-integration/ +[*-v9.0]: ${prefix}/${version}/quick-start-sinatra/ -> ${base}/${version}/tutorials/getting-started-sinatra/ +[*-v9.0]: ${prefix}/${version}/quick-start-rails/ -> ${base}/${version}/tutorials/getting-started-rails7/ +[*-v9.0]: ${prefix}/${version}/data-modeling/documents/ -> ${base}/${version}/tutorials/documents/ +[*-v9.0]: ${prefix}/${version}/security/encryption/ -> ${base}/${version}/tutorials/automatic-encryption/ +[*-v9.0]: ${prefix}/${version}/data-modeling/field-types/ -> ${base}/${version}/reference/fields/ +[*-v9.0]: ${prefix}/${version}/data-modeling/inheritance/ -> ${base}/${version}/reference/inheritance/ +[*-v9.0]: ${prefix}/${version}/data-modeling/associations/ -> ${base}/${version}/reference/associations/ +[*-v9.0]: ${prefix}/${version}/data-modeling/validation/ -> ${base}/${version}/reference/validation/ +[*-v9.0]: ${prefix}/${version}/configuration/collection-config/ -> ${base}/${version}/reference/collection-configuration/ +[*-v9.0]: ${prefix}/${version}/data-modeling/indexes/ -> ${base}/${version}/reference/indexes/ +[*-v9.0]: ${prefix}/${version}/configuration/sharding/ -> ${base}/${version}/reference/sharding/ +[*-v9.0]: ${prefix}/${version}/interact-data/crud/ -> ${base}/${version}/reference/crud/ +[*-v9.0]: ${prefix}/${version}/interact-data/specify-query/ -> ${base}/${version}/reference/queries/ +[*-v9.0]: ${prefix}/${version}/interact-data/text-search/ -> ${base}/${version}/reference/text-search/ +[*-v9.0]: ${prefix}/${version}/interact-data/aggregation/ -> ${base}/${version}/reference/aggregation/ +[*-v9.0]: ${prefix}/${version}/configuration/persistence-config/ -> ${base}/${version}/reference/persistence-configuration/ +[*-v9.0]: ${prefix}/${version}/interact-data/nested-attributes/ -> ${base}/${version}/reference/nested-attributes/ +[*-v9.0]: ${prefix}/${version}/data-modeling/callbacks/ -> ${base}/${version}/reference/callbacks/ +[*-v9.0]: ${prefix}/${version}/interact-data/transaction/ -> ${base}/${version}/reference/transactions/ +[*-v9.0]: ${prefix}/${version}/whats-new/ -> ${base}/${version}/release-notes/mongoid-9.0/ +[*-v9.0]: ${prefix}/${version}/code-documentation/ -> ${base}/${version}/contributing/code-documentation/ +[*-v9.0]: ${prefix}/${version}/issues-and-help/ -> ${base}/${version}/contributing/contributing-guidelines/ +[*-v9.0]: ${prefix}/${version}/integrations-tools/external-resources/ -> ${base}/${version}/ecosystem/ + +# Redirects for old page URLs in standardized (new) docs +[master]: ${prefix}/${version}/installation/ -> ${base}/${version}/#quick-start +[master]: ${prefix}/${version}/reference/compatibility/ -> ${base}/${version}/compatibility/ +[master]: ${prefix}/${version}/reference/configuration/ -> ${base}/${version}/configuration/ +[master]: ${prefix}/${version}/reference/rails-integration/ -> ${base}/${version}/integrations-tools/rails-integration/ +[master]: ${prefix}/${version}/tutorials/getting-started-sinatra/ -> ${base}/${version}/quick-start-sinatra/ +[master]: ${prefix}/${version}/tutorials/getting-started-rails7/ -> ${base}/${version}/quick-start-rails/ +[master]: ${prefix}/${version}/tutorials/getting-started-rails6/ -> ${base}/${version}/quick-start-rails/ +[master]: ${prefix}/${version}/tutorials/documents/ -> ${base}/${version}/data-modeling/documents/ +[master]: ${prefix}/${version}/tutorials/common-errors/ -> ${base}/${version}/ +[master]: ${prefix}/${version}/tutorials/automatic-encryption/ -> ${base}/${version}/security/encryption/ +[master]: ${prefix}/${version}/reference/fields/ -> ${base}/${version}/data-modeling/field-types/ +[master]: ${prefix}/${version}/reference/inheritance/ -> ${base}/${version}/data-modeling/inheritance/ +[master]: ${prefix}/${version}/reference/associations/ -> ${base}/${version}/data-modeling/associations/ +[master]: ${prefix}/${version}/reference/validation/ -> ${base}/${version}/data-modeling/validation/ +[master]: ${prefix}/${version}/reference/collection-configuration/ -> ${base}/${version}/configuration/collection-config/ +[master]: ${prefix}/${version}/reference/indexes/ -> ${base}/${version}/data-modeling/indexes/ +[master]: ${prefix}/${version}/reference/sharding/ -> ${base}/${version}/configuration/sharding/ +[master]: ${prefix}/${version}/reference/crud/ -> ${base}/${version}/interact-data/crud/ +[master]: ${prefix}/${version}/reference/queries/ -> ${base}/${version}/interact-data/specify-query/ +[master]: ${prefix}/${version}/reference/text-search/ -> ${base}/${version}/interact-data/text-search/ +[master]: ${prefix}/${version}/reference/aggregation/ -> ${base}/${version}/interact-data/aggregation/ +[master]: ${prefix}/${version}/reference/map-reduce/ -> ${base}/${version}/interact-data/aggregation/ +[master]: ${prefix}/${version}/reference/persistence-configuration/ -> ${base}/${version}/configuration/persistence-config/ +[master]: ${prefix}/${version}/reference/nested-attributes/ -> ${base}/${version}/interact-data/nested-attributes/ +[master]: ${prefix}/${version}/reference/callbacks/ -> ${base}/${version}/data-modeling/callbacks/ +[master]: ${prefix}/${version}/reference/sessions/ -> ${base}/${version}/interact-data/transaction/ +[master]: ${prefix}/${version}/reference/transactions/ -> ${base}/${version}/interact-data/transaction/ +[master]: ${prefix}/${version}/release-notes/upgrading/ -> ${base}/${version}/whats-new/ +[master]: ${prefix}/${version}/release-notes/mongoid-9.0/ -> ${base}/${version}/whats-new/ +[master]: ${prefix}/${version}/contributing/code-documentation/ -> ${base}/${version}/code-documentation/ +[master]: ${prefix}/${version}/contributing/contributing-guidelines/ -> ${base}/${version}/issues-and-help/ +[master]: ${prefix}/${version}/additional-resources/ -> ${base}/${version}/integrations-tools/external-resources/ +[master]: ${prefix}/${version}/ecosystem/ -> ${base}/${version}/integrations-tools/external-resources/ diff --git a/source/api.txt b/source/api.txt index eb027514..8a1121db 100644 --- a/source/api.txt +++ b/source/api.txt @@ -12,4 +12,4 @@ API Documentation :maxdepth: 1 {+odm+} <{+api+}> - Ruby Driver <{+ruby-api+}> + {+language+} Driver <{+ruby-api+}> diff --git a/source/code-documentation.txt b/source/code-documentation.txt index dd18d384..5f10ea21 100644 --- a/source/code-documentation.txt +++ b/source/code-documentation.txt @@ -198,7 +198,8 @@ Type Declaration # @param [ Hash ] hash A Hash whose keys are Symbols, # and whose values are boolean values. -- **Ruby Values:** Specific values may be denoted in the type using Ruby syntax. +- **{+language+} Values:** Specific values may be denoted in the type + using {+language+} syntax. .. code-block:: ruby @@ -206,7 +207,7 @@ Type Declaration - **True, False, and Nil:** Use ``true``, ``false``, and ``nil`` rather than ``TrueClass``, ``FalseClass``, and ``NilClass``. Do not use ``Boolean`` as a type - since it does not exist in Ruby. + since it does not exist in {+language+}. .. code-block:: ruby diff --git a/source/compatibility.txt b/source/compatibility.txt index d44c016b..9ce29e41 100644 --- a/source/compatibility.txt +++ b/source/compatibility.txt @@ -38,7 +38,7 @@ MongoDB Compatibility The following compatibility table specifies the recommended version or versions of {+odm+} that you can use with a specific version of MongoDB. To use -features of a particular MongoDB Server version, both the +features of a particular {+mdb-server+} version, both the {+ruby-driver+} and {+odm+} must be compatible with that MongoDB version. To learn about the driver's MongoDB compatibility details, see :ruby:`Compatibility ` diff --git a/source/configuration/app-config.txt b/source/configuration/app-config.txt index 05ad16ea..013ecf56 100644 --- a/source/configuration/app-config.txt +++ b/source/configuration/app-config.txt @@ -331,14 +331,14 @@ functionality is provided by the {+ruby-driver+}, which implements the following supported algorithms: - `Zstandard `__ (*Recommended*): To use ``zstandard`` - compression, you must install the `zstd-ruby - `__ library. This compressor + compression, you must install the :rubygems:`zstd-ruby + ` library. This compressor produces the highest compression at the same CPU consumption compared to the other compressors. - :github:`Snappy `: To use ``snappy`` compression, you - must install the `snappy `__ library. + must install the :rubygems:`snappy ` library. - `Zlib `__: To use ``zlib`` compression, you - must install the `zlib `__ library. + must install the :rubygems:`zlib ` library. To use wire protocol compression, configure the driver options in your ``mongoid.yml`` file: @@ -359,4 +359,4 @@ use compression, even if the required dependencies for one or more compressors are installed. The driver chooses the first compressor, if you specify multiple, that -is supported by the MongoDB Server. +is supported by your MongoDB deployment. diff --git a/source/configuration/collection-config.txt b/source/configuration/collection-config.txt index 9d2c9e22..2cf28c06 100644 --- a/source/configuration/collection-config.txt +++ b/source/configuration/collection-config.txt @@ -29,7 +29,7 @@ Configure Collection Options You can specify configuration options for a collection by using the ``:collection_options`` argument with the ``store_in`` macro. The ``:collection_options`` argument accepts any collection option that -your {+ruby-driver+} and MongoDB server version supports. +your {+ruby-driver+} and {+mdb-server+} versions support. .. note:: diff --git a/source/configuration/persistence-config.txt b/source/configuration/persistence-config.txt index 06414b5b..5452de0b 100644 --- a/source/configuration/persistence-config.txt +++ b/source/configuration/persistence-config.txt @@ -30,8 +30,9 @@ the persistence configuration of a model class. .. note:: - "Client" refers to a host configuration defined under ``clients`` in your - ``mongoid.yml`` file. Most applications use a single client named ``default``. + The term "client" refers to a host configuration defined under + ``clients`` in your ``mongoid.yml`` file. Most applications use + a single client named ``default``. Default Collection Name ----------------------- @@ -48,12 +49,12 @@ the ``Person`` class, the corresponding collection is named ``people``. However, the default rules of pluralization don't always work. For example, suppose your model is named ``Rey``. The plural form of this word in -Spanish is "reyes," but the default collection name is "reys." +Spanish is ``reyes``, but the default collection name is ``reys``. You can create a new pluralization rule for your model class by calling the -`ActiveSupport::Inflector::Inflections.plural() `__ +`ActiveSupport::Inflector::Inflections.plural `__ instance method and passing the singular and plural forms of your class name. -The following example specifies "reyes" as the plural of "rey": +The following example specifies ``reyes`` as the plural of ``rey``: .. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby @@ -65,8 +66,9 @@ collection. .. note:: BSON Document Structure - When {+odm+} stores a document in a database, it serializes the Ruby object - to a BSON document that has the following structure: + When {+odm+} stores a document in a database, it serializes the + {+language+} object to a BSON document that has the following + structure: .. literalinclude:: /includes/configuration/persistence-configuration.rb :language: ruby diff --git a/source/configuration/query-cache-config.txt b/source/configuration/query-cache-config.txt index ec89cd39..5f8aaefd 100644 --- a/source/configuration/query-cache-config.txt +++ b/source/configuration/query-cache-config.txt @@ -26,6 +26,8 @@ activate the :ref:`mongoid-query-cache` for each request to store your query results. This can improve your application speed and efficiency by reducing the number of calls your application must make to the database. +.. _mongoid-query-cache-rack: + Enable Query Cache for Rack Web Requests ---------------------------------------- diff --git a/source/configuration/sharding.txt b/source/configuration/sharding.txt index 95c259a0..140ea967 100644 --- a/source/configuration/sharding.txt +++ b/source/configuration/sharding.txt @@ -153,7 +153,6 @@ errors, check the output of the {+odm+} logger configured for your application. run the :manual:`flushRouterConfig ` command on each ``mongos`` node. - Additional Information ---------------------- diff --git a/source/data-modeling/associations.txt b/source/data-modeling/associations.txt index 88d65315..80afd719 100644 --- a/source/data-modeling/associations.txt +++ b/source/data-modeling/associations.txt @@ -73,7 +73,7 @@ class is present in your parent class, as shown in the following example: :emphasize-lines: 6 To learn more about validations in {+odm+}, see the :ref:`Validations -` guide. +` guide. Has Many ~~~~~~~~ @@ -110,7 +110,7 @@ class is present in your parent class, as shown in the following example: :emphasize-lines: 6 To learn more about validations in {+odm+}, see the :ref:`Validations -` guide. +` guide. Retrieve Association Information ```````````````````````````````` @@ -396,7 +396,7 @@ are embedded in a ``Band`` class. The query returns documents that have a :start-after: # start-embedded-query :end-before: # end-embedded-query -You can use the ``pluck()`` projection method to retrieve embedded documents +You can use the ``pluck`` projection method to retrieve embedded documents without retrieving their associated parent documents, as shown in the following example: @@ -434,13 +434,14 @@ Embedded matching on loaded documents has the following known limitations: - Embedded matching is not implemented for the following features: - - :ref:`Text search ` - - :manual:`Geospatial query operators ` - - Operators that execute JavaScript code, such as :manual:`$where ` + - :ref:`Text search ` + - :manual:`Geospatial queries ` + - Operators that execute JavaScript code, such as :manual:`$where + ` - Operators that are implemented through other server functionality, such as - :manual:`$expr ` + :manual:`$expr ` and :manual:`$jsonSchema - ` + ` - {+odm+} expands ``Range`` arguments to hashes with ``$gte`` and ``$lte`` conditions. This can lead to invalid queries in some cases and raises a an @@ -450,7 +451,7 @@ Embedded matching on loaded documents has the following known limitations: as a pattern while also providing options to the ``$options`` field. You can only provide options if the regular expression pattern is a string. -- MongoDB Server versions 4.0 and earlier do not strictly validate ``$type`` +- {+mdb-server+} versions 4.0 and earlier do not strictly validate ``$type`` arguments. Omit _id Fields @@ -477,14 +478,14 @@ Delete Embedded Associations You can delete child documents from ``embeds_many`` associations by using one of the following methods: -- ``clear()`` -- ``delete_all()`` -- ``destroy_all()`` +- ``clear`` +- ``delete_all`` +- ``destroy_all`` -The ``clear()`` method uses the :manual:`$unset operator +The ``clear`` method uses the :manual:`$unset operator ` operator to remove an entire embedded -association from the parent document. The ``clear()`` method does not run any -``destroy`` callbacks. The following example uses the ``clear()`` +association from the parent document. The ``clear`` method does not run any +``destroy`` callbacks. The following example uses the ``clear`` method to remove all embedded associations from the ``Band`` class: .. literalinclude:: /includes/data-modeling/associations.rb @@ -492,12 +493,12 @@ method to remove all embedded associations from the ``Band`` class: :start-after: # start-embedded-clear :end-before: # end-embedded-clear -The ``delete_all()`` method uses the :manual:`$pullAll operator +The ``delete_all`` method uses the :manual:`$pullAll operator ` operator to remove documents in an -embedded association. ``delete_all()`` loads the association if it has not +embedded association. ``delete_all`` loads the association if it has not yet been loaded, then only removes the documents that exist in the application. -The ``delete_all()`` method does not run any ``destroy`` callbacks. -The following example uses the ``delete_all()`` method to remove all embedded +The ``delete_all`` method does not run any ``destroy`` callbacks. +The following example uses the ``delete_all`` method to remove all embedded ``Album`` documents from the ``Band`` class: .. literalinclude:: /includes/data-modeling/associations.rb @@ -505,10 +506,10 @@ The following example uses the ``delete_all()`` method to remove all embedded :start-after: # start-embedded-delete-all :end-before: # end-embedded-delete-all -The ``destroy_all()`` method also uses the :manual:`$pullAll operator +The ``destroy_all`` method also uses the :manual:`$pullAll operator ` operator to remove documents in an embedded association. It also runs any ``destroy`` callbacks that are defined on -the associated documents. The following example uses the ``destroy_all()`` +the associated documents. The following example uses the ``destroy_all`` method to remove all embedded ``Album`` documents from the ``Band`` class: .. literalinclude:: /includes/data-modeling/associations.rb diff --git a/source/data-modeling/documents.txt b/source/data-modeling/documents.txt index 97442968..f1e06630 100644 --- a/source/data-modeling/documents.txt +++ b/source/data-modeling/documents.txt @@ -86,10 +86,11 @@ The document appears in MongoDB as follows: Additional Information ---------------------- +To learn more about the field types that you can use in {+odm+} models, see +the :ref:`mongoid-field-types` guide. + To learn how to access and change your MongoDB data, see the :ref:`mongoid-interact-data` guides. To learn more about how to model your data by using {+odm+} models, see the :ref:`mongoid-data-modeling` guides. - -.. TODO Add link to field types guide. diff --git a/source/data-modeling/field-behaviors.txt b/source/data-modeling/field-behaviors.txt index daf9dc23..5600af31 100644 --- a/source/data-modeling/field-behaviors.txt +++ b/source/data-modeling/field-behaviors.txt @@ -83,6 +83,8 @@ following example: Always set the ``pre-processed`` option to ``true`` to set a default ``Proc`` value for the ``_id`` field. +.. _mongoid-field-behaviors-storage-names: + Specify Storage Names --------------------- @@ -103,6 +105,8 @@ following example creates a field called ``name`` that {+odm+} stores in the dat {+odm+} stores the ``name`` field as ``"n"``, but you can still access the field as ``name`` in your application. +.. _mongoid-field-behaviors-aliases: + Field Aliases ------------- @@ -222,11 +226,15 @@ you attempt to explicitly update a read-only field, {+odm+} raises a Calls to atomic persistence operators, such as ``bit`` and ``inc``, still persist changes to the read-only field. +To learn about specifying entire models as read-only, see the +:ref:`mongoid-crud-read-only` section of the Perform Data Operations +guide. + Localize Fields --------------- -{+odm+} supports localized fields by using the `i18n gem -`__. When you localize a field, {+odm+} +{+odm+} supports localized fields by using the :github:`i18n gem +`. When you localize a field, {+odm+} stores the field as a hash of locale keys and values. Accessing the fields behaves in the same way as a string value. You can localize fields of any field type. @@ -247,8 +255,8 @@ method: :start-after: # start-localized-translations :end-before: # end-localized-translations -You can specify fallbacks for localized fields by enabling the `i18n fallbacks -`__ feature. +You can specify fallbacks for localized fields by enabling the +:github:`i18n fallbacks ` feature. Enable fallbacks in a Rails application by setting the ``config.i18n.fallbacks`` configuration setting in your environment and setting the fallback languages: diff --git a/source/data-modeling/field-types.txt b/source/data-modeling/field-types.txt index 1d4df189..5967162e 100644 --- a/source/data-modeling/field-types.txt +++ b/source/data-modeling/field-types.txt @@ -30,7 +30,8 @@ when retrieving a document from the database, {+odm+} translates a BSON ``double`` type to use the {+language+} ``Float`` type. When you save the document again, {+odm+} converts the field back to a BSON ``double``. -.. TODO: To learn more about modeling documents in {+odm+}, see :ref:`mongoid-modeling-documents`. +To learn more about modeling documents in {+odm+}, see the +:ref:`mongoid-modeling-documents` guide. .. note:: @@ -134,10 +135,8 @@ Time You can store values as BSON ``Time`` instances by using the ``Time`` field value. ``Time`` fields are stored in the time zone configured for your application. To -learn more about configuring time zones, see the :ref:`Time Zones ` -guide. - -.. TODO: Update Time Zones guide ref if it's changed during standardization +learn more about configuring time zones, see the :ref:`mongoid-config-time-zones` +section of the Application Configuration guide. The following example creates a ``Voter`` class and specifies that the value of the ``registered_at`` field is a ``Time`` type: @@ -151,11 +150,10 @@ The following example creates a ``Voter`` class and specifies that the value of Storing a ``Date`` or ``DateTime`` value in a field specified as ``Time`` converts the value to ``Time`` when assigned. If you store a string in a - ``Time`` field, {+odm+} parses the string by using the ``Time.parse()`` - method. - -.. TODO: Add this to the note: -.. To learn more about how {+odm+} converts queries, see :ref:`` + ``Time`` field, {+odm+} parses the string by using the ``Time.parse`` + method. To learn more about how {+odm+} converts queries, see the + :ref:`mongoid-query-field-type-conversions` section of the Specify a + Query guide. Date ~~~~ @@ -182,12 +180,11 @@ to ``Date`` before assigning them to the field. .. note:: When a database contains a string value for a ``Date`` field, the driver - parses the value by using the ``Time.parse()`` method, then discards the time - portion. ``Time.parse()`` considers values without time zones to be in local - time. - -.. TODO: Add this to the note: -.. To learn more about how {+odm+} converts queries, see :ref:`` + parses the value by using the ``Time.parse`` method, then discards the time + portion. ``Time.parse`` considers values without time zones to be in local + time. To learn more about how {+odm+} converts queries, see the + :ref:`mongoid-query-field-type-conversions` section of the Specify a + Query guide. DateTime ~~~~~~~~ @@ -224,14 +221,12 @@ value by using the timezone configured as the default for your application: :start-after: # start-datetime-string :end-before: # end-datetime-string -To learn more about configuring time zones, see the :ref:`Time Zones ` -guide. - -.. TODO: Update Time Zones guide ref if it's changed during standardization +To learn more about configuring time zones, see the :ref:`mongoid-config-time-zones` +section of the Application Configuration guide. .. note:: - {+odm+} parses string values into ``DateTime`` by using the ``Time.parse()`` + {+odm+} parses string values into ``DateTime`` by using the ``Time.parse`` method, which considers values without time zones to be in local time. Timestamps @@ -270,7 +265,7 @@ the ``::Short`` option when including the module: :end-before: # end-timestamps-short You can disable creating the timestamp field for specific operations by calling -the ``timeless()`` method on the method call. The following example disables the +the ``timeless`` method on the method call. The following example disables the timestamps for the ``save`` operation: .. literalinclude:: /includes/data-modeling/field-types.rb @@ -284,7 +279,7 @@ Regexp You can store regular expressions in a field by using the ``Regexp`` type. While MongoDB implements `Perl Compatible Regular Expressions (PCRE) `__, -{+odm+} uses {+language+}'s `Onigmo `__ library. PCRE and +{+odm+} uses {+language+}'s :github:`Onigmo ` library. PCRE and Onigmo provide generally similar functionality, but there are several syntax differences. For example, Onigmo uses ``\A`` and ``\z`` to match the beginning and end of a string, while PCRE uses ``^`` and ``$``. @@ -292,7 +287,7 @@ end of a string, while PCRE uses ``^`` and ``$``. When you declare a field as a ``Regexp``, {+odm+} converts {+language+} regular expressions to BSON regular expressions when storing the result into your database. The database returns the field as a ``Bson::Regexp::Raw`` instance. -You can use the ``compile()`` method on ``BSON::Regexp::Raw`` instances to convert +You can use the ``compile`` method on ``BSON::Regexp::Raw`` instances to convert the data back to a {+language+} regular expression. The following example creates a ``Token`` class and specifies the ``pattern`` @@ -307,9 +302,9 @@ field as a ``Regexp``: Converting a BSON regular expression to a {+language+} regular expression might produce a different regular expression than the original. This difference is - due to the differences between the Onigmo and PCRE syntaxes. - -.. TODO: To learn more about regular expressions in {+odm+}, see :ref:`mongoid-regular-expressions`. + due to the differences between the Onigmo and PCRE syntaxes. To learn + more about regular expressions in {+odm+}, see the + :ref:`mongoid-query-regex` section of the Specify a Query guide. BigDecimal ~~~~~~~~~~ @@ -472,11 +467,11 @@ You can create custom field types and define how {+odm+} serializes and deserializes them. To create a custom field type, define a class that implements the following methods: -- ``mongoize()``: Takes an instance of your custom type and converts it to +- ``mongoize``: Takes an instance of your custom type and converts it to an object that MongoDB can store. -- ``demongoize()``: Takes an object from MongoDB and converts it to an +- ``demongoize``: Takes an object from MongoDB and converts it to an instance of your custom type. -- ``evolve()``: Takes an instance of your custom type and converts it to a +- ``evolve``: Takes an instance of your custom type and converts it to a criteria that MongoDB can use to query the database. The following example creates a custom field type called ``Point`` and @@ -487,16 +482,16 @@ implements the preceding methods: :start-after: # start-custom-field-type :end-before: # end-custom-field-type -In the preceding example, the ``mongoize()`` *instance method* accepts an instance +In the preceding example, the ``mongoize`` *instance method* accepts an instance of your custom type object and converts it to an ``Array`` to store in the -database. The ``mongoize()`` *class method* accepts objects of all types and +database. The ``mongoize`` *class method* accepts objects of all types and converts them to similar types that can be stored in the database. {+odm+} uses -the ``mongoize()`` class method when it calls the getter and setter methods. +the ``mongoize`` class method when it calls the getter and setter methods. -The ``demongoize()`` method converts the stored ``Array`` value into the custom +The ``demongoize`` method converts the stored ``Array`` value into the custom ``Point`` type. The {+odm+} uses this method when it calls the getter. -The ``evolve()`` method converts the custom ``Point`` type into a queryable +The ``evolve`` method converts the custom ``Point`` type into a queryable ``Array`` type, and converts all other types to ``object``. {+odm+} uses this method when it calls a method that queries the database. @@ -516,10 +511,12 @@ color in the application, but stores the color as an integer in the database: :start-after: # start-phantom-field-type :end-before: # end-phantom-field-type +.. _mongoid-field-types-dynamic: + Dynamic Fields -------------- -You can instruct {+odm+} to create fields dynamically by inluding the +You can instruct {+odm+} to create fields dynamically by including the ``Mongoid::Attributes::Dynamic`` module in your model. This allows {+odm+} to create fields based on an arbitrary hash, or based on the documents already stored in the database. @@ -558,24 +555,19 @@ string to indicate a query operator. Because of this, you should avoid using the characters in your field names. If your application requires the use of these characters, you can access the -fields by calling the ``send()`` method. The following example creates a ``User`` +fields by calling the ``send`` method. The following example creates a ``User`` class with fields that contain reserved characters. It then accesses the fields -by using the ``send()`` method: +by using the ``send`` method: .. literalinclude:: /includes/data-modeling/field-types.rb :language: ruby :start-after: # start-reserved-characters :end-before: # end-reserved-characters -You can also access these fields by calling the ``read_attribute()`` method. +You can also access these fields by calling the ``read_attribute`` method. .. important:: Because updating and replacing fields containing these reserved characters requires special operators, calling getters and setters on these fields raises an ``InvalidDotDollarAssignment`` exception. - -.. Additional Information -.. ---------------------- - -.. TODO: Add Additional Information section with links to relevant pages once they are standardized diff --git a/source/data-modeling/indexes.txt b/source/data-modeling/indexes.txt index 736e0141..dcf39899 100644 --- a/source/data-modeling/indexes.txt +++ b/source/data-modeling/indexes.txt @@ -1,4 +1,5 @@ .. _mongoid-optimize-queries-with-indexes: +.. _mongoid-indexes: ============================= Optimize Queries With Indexes diff --git a/source/data-modeling/inheritance.txt b/source/data-modeling/inheritance.txt index 26654874..08441ffd 100644 --- a/source/data-modeling/inheritance.txt +++ b/source/data-modeling/inheritance.txt @@ -104,7 +104,7 @@ You might change the discriminator key from the default field name You can change the discriminator key on the class level or on the global level. To change the discriminator key on the class level, you can set the custom key name on the parent class by using the -``discriminator_key()`` method. +``discriminator_key`` method. The following example demonstrates how to set a custom discriminator key when defining a model class: @@ -166,7 +166,7 @@ Change the Discriminator Value ------------------------------ You can customize the value that {+odm+} sets as the discriminator value -in MongoDB. Use the ``discriminator_value()`` method when defining a +in MongoDB. Use the ``discriminator_value`` method when defining a class to customize the discriminator value, as shown in the following example: @@ -201,9 +201,9 @@ Embedded Associations --------------------- You can create any type of parent class or child class in an embedded -association by assignment or by using the ``build()`` and ``create()`` +association by assignment or by using the ``build`` and ``create`` methods. You can pass desired model class as the second parameter to the -``build()`` and ``create()`` methods to instruct {+odm+} to create that +``build`` and ``create`` methods to instruct {+odm+} to create that specific instance as an emdedded document. The following code creates an instance of ``Employee``, then @@ -262,12 +262,12 @@ Persistence Contexts You can change the persistence context of a child class from the persistence context of its parent to store the document in a -different location than the default. By using the ``store_in()`` method, +different location than the default. By using the ``store_in`` method, you can store an instance of a child class in a different collection, database, or cluster than an instance of the parent model. The following model definitions demonstrate how to use the -``store_in()`` method to store instances of ``Employee`` and ``Manager`` +``store_in`` method to store instances of ``Employee`` and ``Manager`` in a different collection than the ``people`` collection: .. code-block:: ruby @@ -304,4 +304,5 @@ are stored in the collection associated with the parent class. Additional Information ---------------------- -.. TODO add links to persistence docs +To learn more about configuring the target collection for your +operations, see the :ref:`mongoid-persistence` guide. diff --git a/source/data-modeling/validation.txt b/source/data-modeling/validation.txt index 6909df02..96ac5474 100644 --- a/source/data-modeling/validation.txt +++ b/source/data-modeling/validation.txt @@ -255,9 +255,8 @@ Validate Associations You can use the ``validates_associated`` helper to validate any associations that your model has. When you include this validation rule, {+odm+} validates any association documents any time you try to -save an instance. - -.. TODO link to associations page +save an instance. To learn more about associations, see the +:ref:`mongoid-associations` guide. This example defines an association validation rule on the ``Author`` model to run the validation rules for the embedded ``Book`` @@ -301,9 +300,9 @@ Behavior database. The following methods trigger your validation rules, so {+odm+} saves the object to the database only if it passes validation: -- ``create()`` -- ``save()`` -- ``update()`` +- ``create`` +- ``save`` +- ``update`` When you use the ``!``-suffixed version of the preceding methods, {+odm+} returns an ``Mongoid::Errors::Validations`` exception if @@ -312,7 +311,7 @@ validation fails for an object. Trigger Validation ~~~~~~~~~~~~~~~~~~ -You can run validations manually by using the ``valid?()`` method. This +You can run validations manually by using the ``valid?`` method. This method returns ``true`` if the object passes validation, and ``false`` otherwise: @@ -323,14 +322,17 @@ method returns ``true`` if the object passes validation, and :emphasize-lines: 7, 11, 14 :dedent: -{+odm+} behaves differently from Active Record when running ``valid?()`` -on persisted data. Active Record's ``valid?()`` runs all -validations, but {+odm+}'s ``valid?()`` runs validations only on +{+odm+} behaves differently from Active Record when running ``valid?`` +on persisted data. Active Record's ``valid?`` runs all +validations, but {+odm+}'s ``valid?`` runs validations only on documents that are in memory to optimize performance. Additional Information ---------------------- +To learn more about the field types that you can use in {+odm+} models, see +the :ref:`mongoid-field-types` guide. + To learn more about validation methods and macros in {+odm+}, see the :mongoid-api:`Mongoid::Validatable ` module reference in the API documentation. @@ -339,5 +341,3 @@ To view a full list of validations helpers in Active Record, see the `ActiveModel::Validations::HelperMethods `__ reference in the Rails API documentation. - -.. TODO link to field types guide. diff --git a/source/includes/data-modeling/field-behaviors.rb b/source/includes/data-modeling/field-behaviors.rb index 94e9d4dd..f1f59e47 100644 --- a/source/includes/data-modeling/field-behaviors.rb +++ b/source/includes/data-modeling/field-behaviors.rb @@ -1,22 +1,22 @@ # start-field-default class Order - include Mongoid::Document + include Mongoid::Document - field :state, type: String, default: 'created' + field :state, type: String, default: 'created' end # end-field-default # start-field-default-processed class Order - include Mongoid::Document + include Mongoid::Document - field :fulfill_by, type: Time, default: ->{ Time.now + 3.days } + field :fulfill_by, type: Time, default: ->{ Time.now + 3.days } end # end-field-default-processed # start-field-default-self field :fulfill_by, type: Time, default: ->{ - self.submitted_at + 4.hours + self.submitted_at + 4.hours } # end-field-default-self @@ -27,56 +27,56 @@ class Order # start-field-as class Band - include Mongoid::Document - field :n, as: :name, type: String + include Mongoid::Document + field :n, as: :name, type: String end # end-field-as # start-field-alias class Band - include Mongoid::Document - field :name, type: String - alias_attribute :n, :name + include Mongoid::Document + field :name, type: String + alias_attribute :n, :name end # end-field-alias # start-field-unalias class Band - unalias_attribute :n + unalias_attribute :n end # end-field-unalias # start-field-overwrite class Person - include Mongoid::Document - field :name - field :name, type: String, overwrite: true + include Mongoid::Document + field :name + field :name, type: String, overwrite: true end # end-field-overwrite # start-custom-id class Band - include Mongoid::Document - field :name, type: String - field :_id, type: String, default: ->{ name } + include Mongoid::Document + field :name, type: String + field :_id, type: String, default: ->{ name } end # end-custom-id # start-custom-getter-setter class Person - include Mongoid::Document - field :name, type: String + include Mongoid::Document + field :name, type: String - # Custom getter for 'name' to return the name in uppercase - def name - read_attribute(:name).upcase if read_attribute(:name) - end + # Custom getter for 'name' to return the name in uppercase + def name + read_attribute(:name).upcase if read_attribute(:name) + end - # Custom setter for 'name' to store the name in lowercase - def name=(value) - write_attribute(:name, value.downcase) - end + # Custom setter for 'name' to store the name in lowercase + def name=(value) + write_attribute(:name, value.downcase) end +end # end-custom-getter-setter # start-localized-field diff --git a/source/includes/data-modeling/indexes.rb b/source/includes/data-modeling/indexes.rb index 9eebaab9..ab68b9ca 100644 --- a/source/includes/data-modeling/indexes.rb +++ b/source/includes/data-modeling/indexes.rb @@ -1,12 +1,12 @@ # start create index class Restaurant - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :cuisine, type: String - field :borough, type: String + field :name, type: String + field :cuisine, type: String + field :borough, type: String - index({ cuisine: 1}, { name: "cuisine_index", unique: false }) + index({ cuisine: 1}, { name: "cuisine_index", unique: false }) end Restaurant.create_indexes @@ -14,72 +14,72 @@ class Restaurant # start create alias index class Restaurant - include Mongoid::Document + include Mongoid::Document - field :borough, as: :b + field :borough, as: :b - index({ b: 1}, { name: "borough_index" }) + index({ b: 1}, { name: "borough_index" }) end # end create alias index # start create embedded index class Address - include Mongoid::Document + include Mongoid::Document - field :street, type: String + field :street, type: String end class Restaurant - include Mongoid::Document + include Mongoid::Document - embeds_many :addresses - index({"addresses.street": 1}) + embeds_many :addresses + index({"addresses.street": 1}) end # end create embedded index # start create compound index class Restaurant - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :borough, type: String + field :name, type: String + field :borough, type: String - index({borough: 1, name: -1}, { name: "compound_index"}) + index({borough: 1, name: -1}, { name: "compound_index"}) end # end create compound index # start create 2dsphere index class Restaurant - include Mongoid::Document + include Mongoid::Document - field :location, type: Array + field :location, type: Array - index({location: "2dsphere"}, { name: "location_index"}) + index({location: "2dsphere"}, { name: "location_index"}) end # end create 2dsphere index # start create sparse index class Restaurant - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :cuisine, type: String - field :borough, type: String + field :name, type: String + field :cuisine, type: String + field :borough, type: String - index({ borough: 1}, { sparse: true }) + index({ borough: 1}, { sparse: true }) end # end create sparse index # start create multiple indexes class Restaurant - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :cuisine, type: String - field :borough, type: String - - index({ name: 1}) - index({ cuisine: -1}) + field :name, type: String + field :cuisine, type: String + field :borough, type: String + + index({ name: 1}) + index({ cuisine: -1}) end Restaurant.create_indexes @@ -91,24 +91,24 @@ class Restaurant # start create atlas search index class Restaurant - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :cuisine, type: String - field :borough, type: String - - search_index :my_search_index, - mappings: { - fields: { - name: { - type: "string" - }, - cuisine: { - type: "string" - } - }, - dynamic: true - } + field :name, type: String + field :cuisine, type: String + field :borough, type: String + + search_index :my_search_index, + mappings: { + fields: { + name: { + type: "string" + }, + cuisine: { + type: "string" + } + }, + dynamic: true + } end Restaurant.create_search_indexes @@ -120,4 +120,4 @@ class Restaurant #start list atlas search index Restaurant.search_indexes.each { |index| puts index } -# end list atlas search index \ No newline at end of file +# end list atlas search index diff --git a/source/includes/data-modeling/nested_attr.rb b/source/includes/data-modeling/nested_attr.rb index e81a4c4d..d71f613b 100644 --- a/source/includes/data-modeling/nested_attr.rb +++ b/source/includes/data-modeling/nested_attr.rb @@ -18,8 +18,8 @@ class Band band = Band.create( name: 'Tennis', albums_attributes: [ - { name: 'Swimmer', year: 2020 }, - { name: 'Young & Old', year: 2013 }] + { name: 'Swimmer', year: 2020 }, + { name: 'Young & Old', year: 2013 }] ) # end-create-attr @@ -36,7 +36,7 @@ class Band album = band.albums.first # Updates the entry by passing the _id value band.update(albums_attributes: [ - { _id: album._id, year: 2011 } ]) + { _id: album._id, year: 2011 } ]) # end-update-id # start-delete-id @@ -45,14 +45,14 @@ class Band album = band.albums.first # Deletes the entry by passing the _id value band.update(albums_attributes: [ - { _id: album._id, _destroy: true } ]) + { _id: album._id, _destroy: true } ]) # end-delete-id # start-multiple-ops band = Band.where(name: 'Yeah Yeah Yeahs').first # Performs multiple data changes band.update(albums_attributes: [ - { name: 'Show Your Bones', year: 2006 }, - { _id: 1, name: 'Fever To T3ll' }, - { _id: 2, _destroy: true } ]) + { name: 'Show Your Bones', year: 2006 }, + { _id: 1, name: 'Fever To T3ll' }, + { _id: 2, _destroy: true } ]) # end-multiple-ops \ No newline at end of file diff --git a/source/includes/interact-data/scoping.rb b/source/includes/interact-data/scoping.rb index 2e1889a7..1a6f7631 100644 --- a/source/includes/interact-data/scoping.rb +++ b/source/includes/interact-data/scoping.rb @@ -1,12 +1,12 @@ # start-named-scope-1 class Band - include Mongoid::Document + include Mongoid::Document - field :country, type: String - field :genres, type: Array + field :country, type: String + field :genres, type: Array - scope :japanese, ->{ where(country: "Japan") } - scope :rock, ->{ where(:genres.in => [ "rock" ]) } + scope :japanese, ->{ where(country: "Japan") } + scope :rock, ->{ where(:genres.in => [ "rock" ]) } end # end-named-scope-1 @@ -16,12 +16,12 @@ class Band # start-named-scope-2 class Band - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :country, type: String + field :name, type: String + field :country, type: String - scope :based_in, ->(country){ where(country: country) } + scope :based_in, ->(country){ where(country: country) } end # end-named-scope-2 @@ -31,35 +31,35 @@ class Band # start-named-scope-3 class Band - include Mongoid::Document + include Mongoid::Document - def self.on_tour - true - end + def self.on_tour + true + end - scope :on_tour, ->{ where(on_tour: true) } + scope :on_tour, ->{ where(on_tour: true) } end # end-named-scope-3 # start-default-scope-1 class Band - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :active, type: Boolean + field :name, type: String + field :active, type: Boolean - default_scope -> { where(active: true) } + default_scope -> { where(active: true) } end # end-default-scope-1 # start-default-scope-2 class Band - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :on_tour, type: Boolean, default: true + field :name, type: String + field :on_tour, type: Boolean, default: true - default_scope ->{ where(on_tour: false) } + default_scope ->{ where(on_tour: false) } end # Creates a new Band instance in which "on_tour" is "false" @@ -78,11 +78,11 @@ class Band # start-scope-association class Label - include Mongoid::Document + include Mongoid::Document - field :name, type: String + field :name, type: String - embeds_many :bands + embeds_many :bands end class Band @@ -112,13 +112,13 @@ class Band # start-scope-query-behavior class Band - include Mongoid::Document + include Mongoid::Document - field :name - field :touring - field :member_count + field :name + field :touring + field :member_count - default_scope ->{ where(touring: true) } + default_scope ->{ where(touring: true) } end # Combines the condition to the default scope with "and" @@ -139,31 +139,31 @@ class Band # start-override-scope class Band - include Mongoid::Document + include Mongoid::Document - field :country, type: String - field :genres, type: Array + field :country, type: String + field :genres, type: Array - scope :mexican, ->{ where(country: "Mexico") } + scope :mexican, ->{ where(country: "Mexico") } end # end-override-scope # start-override-scope-block Band.with_scope(Band.mexican) do - Band.all + Band.all end # end-override-scope-block # start-class-methods class Band - include Mongoid::Document + include Mongoid::Document - field :name, type: String - field :touring, type: Boolean, default: true + field :name, type: String + field :touring, type: Boolean, default: true - def self.touring - where(touring: true) - end + def self.touring + where(touring: true) + end end Band.touring diff --git a/source/includes/interact-data/transaction.rb b/source/includes/interact-data/transaction.rb index ad8a74de..7f2f9354 100644 --- a/source/includes/interact-data/transaction.rb +++ b/source/includes/interact-data/transaction.rb @@ -1,26 +1,26 @@ # start-example-models class Book - include Mongoid::Document + include Mongoid::Document - field :title, type: String - field :author, type: String - field :length, type: Integer + field :title, type: String + field :author, type: String + field :length, type: Integer end class Film - include Mongoid::Document + include Mongoid::Document - field :title, type: String - field :year, type: Integer + field :title, type: String + field :year, type: Integer end # end-example-models # start-txn-operations # Starts a transaction from the model class Book.transaction do - # Saves new Book and Film instances to MongoDB - Book.create(title: 'Covert Joy', author: 'Clarice Lispector') - Film.create(title: 'Nostalgia', year: 1983) + # Saves new Book and Film instances to MongoDB + Book.create(title: 'Covert Joy', author: 'Clarice Lispector') + Film.create(title: 'Nostalgia', year: 1983) end # Starts a transaction from an instance of Book diff --git a/source/includes/security/encryption.rb b/source/includes/security/encryption.rb index e2ee5555..1fd9290d 100644 --- a/source/includes/security/encryption.rb +++ b/source/includes/security/encryption.rb @@ -11,19 +11,19 @@ class Patient # This field is encrypted by using AEAD_AES_256_CBC_HMAC_SHA_512-Random # algorithm field :passport_id, type: String, encrypt: { - deterministic: false + deterministic: false } # This field is encrypted by using AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic # algorithm field :blood_type, type: String, encrypt: { - deterministic: true + deterministic: true } # This field is encrypted by using AEAD_AES_256_CBC_HMAC_SHA_512-Random # algorithm and a different data key field :ssn, type: Integer, encrypt: { - deterministic: false, key_id: '`__, - `ActiveStorage `__, and + `ActiveStorage <{+active-record-docs+}/active_storage_overview.html>`__, and `ActionMailbox <{+active-record-docs+}/action_mailbox_basics.html>`__ adapters cannot be used alongside {+odm+}. @@ -159,7 +159,7 @@ When creating {+odm+} models, you can define fields in the following ways: - Define fields explicitly -- Use :ref:`dynamic fields ` +- Use :ref:`dynamic fields ` For example, a basic Active Record ``Post`` model might resemble the following: diff --git a/source/integrations-tools/external-resources.txt b/source/integrations-tools/external-resources.txt index 20994095..01a0f4fc 100644 --- a/source/integrations-tools/external-resources.txt +++ b/source/integrations-tools/external-resources.txt @@ -33,59 +33,59 @@ or built on top of {+odm+}. Projects ~~~~~~~~ -- `Workarea Commerce `__ is an +- :github:`Workarea Commerce ` is an enterprise-grade {+ror+} commerce platform that uses {+odm+}. Extension Libraries ~~~~~~~~~~~~~~~~~~~ -- `Mongoid Tree `__ is a tree structure +- :github:`Mongoid Tree ` is a tree structure for {+odm+} documents that uses the materialized path pattern. -- `Mongoid Token `__ generates random, - tokens for {+odm+} documents. +- :github:`Mongoid Token ` generates random, + unique tokens for {+odm+} documents. -- `Mongoid Collection Snapshot - `__ helps maintain +- :github:`Mongoid Collection Snapshot + ` helps maintain collections of processed data in {+odm+} applications. -- `Mongoid Locker `__ provides +- :github:`Mongoid Locker ` provides document-level locking for {+odm+} applications. -- `Mongo Beautiful Logger - `__ is a library that +- :github:`Mongo Beautiful Logger + ` is a library that formats your MongoDB logs. -- `Mongoid Search `__ provides +- :github:`Mongoid Search ` provides full-text search for {+odm+}. -- `Mongoid Fulltext Search `__ +- :github:`Mongoid Fulltext Search ` provides full-text search using n-gram matching for {+odm+}. Integration Libraries ~~~~~~~~~~~~~~~~~~~~~ -- `CarrierWave Mongoid - `__ provides +- :github:`CarrierWave Mongoid + ` provides {+odm+} support for the Carrierwave file-uploads library. -- `Mongoid RSpec `__ provides RSpec +- :github:`Mongoid RSpec ` provides RSpec matchers and macros for {+odm+} applications. -- `RailsAdmin `__ is a Rails engine that +- :github:`RailsAdmin ` is a Rails engine that provides an interface for managing your data. -- `ActiveAdmin Mongoid `__ +- :github:`ActiveAdmin Mongoid ` provides ActiveAdmin hacks to support {+odm+}. -- `Mongoid History `__ is a +- :github:`Mongoid History ` is a multi-user, non-linear history tracker for {+odm+}. -- `Delayed Job Mongoid - `__ is a {+odm+} backend for +- :github:`Delayed Job Mongoid + ` is a {+odm+} backend for ``delayed_job``. -- `Mongo Session Store `__ is a +- :github:`Mongo Session Store ` is a Rails-compatible session store for {+odm+}. Learning Resources @@ -112,7 +112,7 @@ Articles `__: Building a content management application with Sinatra and {+odm+}. -- `How To Create A Ruby API With Sinatra +- `How to Create a {+language+} API With Sinatra `__: Creating a Sinatra API with {+odm+}. @@ -123,5 +123,5 @@ Articles Sample Applications ~~~~~~~~~~~~~~~~~~~ -- `Mongoid Demo `__: A repository - containing sample applications that use {+odm+}. \ No newline at end of file +- :github:`Mongoid Demo `: A repository + containing sample applications that use {+odm+}. diff --git a/source/integrations-tools/rails-integration.txt b/source/integrations-tools/rails-integration.txt index ff703625..1861d47b 100644 --- a/source/integrations-tools/rails-integration.txt +++ b/source/integrations-tools/rails-integration.txt @@ -39,8 +39,8 @@ initializer in the ``config/initializers/mongoid.rb`` file. ``mongoid.yml`` as the default location for {+odm+} configuration when possible. -.. TODO To learn more about available configuration options, - see the :ref:`` section. +To learn more about all available configuration options, +see the :ref:`mongoid-configuration` guides. The following code demonstrates how to create a Rails logger by accessing ``config.mongoid``: @@ -53,7 +53,8 @@ accessing ``config.mongoid``: end end -.. TODO To learn more about logging settings, see the :ref:`` guide. +To learn more about logging settings, see the +:ref:`mongoid-logging-config` guide. Model Preloading ---------------- @@ -98,7 +99,7 @@ This logging is set up automatically in your Rails application. The time indicated in log entries is the time that the MongoDB deployment takes to run MongoDB operations in addition to the time taken to - send commands and receive results from the MongoDB Server. It does + send commands and receive results from {+mdb-server+}. It does not include time taken by the driver and {+odm+} to generate the queries, cast types, or otherwise process the results. diff --git a/source/interact-data/aggregation.txt b/source/interact-data/aggregation.txt index a30317f2..a5b1f6f4 100644 --- a/source/interact-data/aggregation.txt +++ b/source/interact-data/aggregation.txt @@ -86,19 +86,19 @@ pipeline operators: - Method Name * - :manual:`$group ` - - ``group()`` + - ``group`` * - :manual:`$project ` - - ``project()`` + - ``project`` * - :manual:`$unwind ` - - ``unwind()`` + - ``unwind`` To create an aggregation pipeline by using one of the preceding operators, call the corresponding method on an instance of ``Criteria``. Calling the method adds the aggregation operation to the ``pipeline`` atrritbure of the ``Criteria`` instance. To run the aggregation pipeline, pass the ``pipeline`` attribute value -to the ``Collection#aggregate()`` method. +to the ``Collection#aggregate`` method. Example ~~~~~~~ @@ -133,12 +133,12 @@ The following example creates an aggregation pipeline that outputs the states a participant has visited by using the following aggregation operations: -- ``match()``, which find documents in which the ``participants.name`` field +- ``match``, which find documents in which the ``participants.name`` field value is ``"Serenity"`` -- ``unwind()``, which deconstructs the ``states`` array field and outputs a +- ``unwind``, which deconstructs the ``states`` array field and outputs a document for each element in the array -- ``group()``, which groups the documents by the value of their ``states`` field -- ``project()``, which prompts the pipeline to return only the ``_id`` and +- ``group``, which groups the documents by the value of their ``states`` field +- ``project``, which prompts the pipeline to return only the ``_id`` and ``states`` fields .. io-code-block:: @@ -153,7 +153,7 @@ aggregation operations: Aggregation without Builders ---------------------------- -You can use the ``Collection#aggregate()`` method to run aggregation operations that do not have +You can use the ``Collection#aggregate`` method to run aggregation operations that do not have corresponding builder methods by passing in an array of aggregation operations. Using this method to perform the aggregation returns raw ``BSON::Document`` objects rather than ``Mongoid::Document`` model @@ -227,6 +227,6 @@ API Documentation To learn more about any of the methods discussed in this guide, see the following API documentation: -- `group() <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#group-instance_method>`__ -- `project() <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#project-instance_method>`__ -- `unwind() <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#unwind-instance_method>`__ \ No newline at end of file +- `group <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#group-instance_method>`__ +- `project <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#project-instance_method>`__ +- `unwind <{+api+}/Mongoid/Criteria/Queryable/Aggregable.html#unwind-instance_method>`__ \ No newline at end of file diff --git a/source/interact-data/crud.txt b/source/interact-data/crud.txt index 26cc04fe..846c1be6 100644 --- a/source/interact-data/crud.txt +++ b/source/interact-data/crud.txt @@ -135,6 +135,7 @@ Read Operations --------------- You can perform read operations to retrieve documents from a collection. +To learn more about creating query filters to retrieve a subset of your documents, see the :ref:`mongoid-data-specify-query` guide. .. _mongoid-read-attributes: @@ -773,6 +774,8 @@ model and assign it back to the model as shown in the following code: :start-after: start-container-save :end-before: end-container-save +.. _mongoid-crud-read-only: + Read-only Documents ------------------- diff --git a/source/interact-data/modify-results.txt b/source/interact-data/modify-results.txt index 70ce8a52..f4a7af78 100644 --- a/source/interact-data/modify-results.txt +++ b/source/interact-data/modify-results.txt @@ -49,13 +49,13 @@ In MongoDB, *projection* is the process of specifying fields to include or exclude from results. {+odm+} provides the following operators to project fields: -- ``only()``: Specifies fields to include -- ``without()``: Specifies fields to exclude +- ``only``: Specifies fields to include +- ``without``: Specifies fields to exclude Include Fields ~~~~~~~~~~~~~~ -The ``only()`` method retrieves only the specified fields from the +The ``only`` method retrieves only the specified fields from the database. The following code returns only the ``name`` field from documents in @@ -75,7 +75,7 @@ which the value of the ``members`` field is ``4``: If you attempt to reference attributes that have not been loaded, {+odm+} raises a ``Mongoid::Errors::AttributeNotLoaded`` error. -You can also use the ``only()`` method to include fields from embedded +You can also use the ``only`` method to include fields from embedded documents. Consider that the ``Band`` model embeds multiple ``Tour`` objects. You can @@ -96,7 +96,7 @@ Then, you can access the embedded fields from the returned documents: # the first Band in the results bands.first.tours.first -You can pass fields of referenced associations to the ``only()`` method, +You can pass fields of referenced associations to the ``only`` method, but the projection is ignored when loading the embedded objects. {+odm+} loads all fields of the referenced associations. For example, when you access the embedded ``Tour`` object as shown in the preceding code, @@ -110,7 +110,7 @@ access the embedded ``Tour`` object as shown in the preceding code, If a document contains ``has_one`` or ``has_and_belongs_to_many`` associations, and you want {+odm+} to load those associations when -you call the ``only()`` method, you must include the fields with foreign +you call the ``only`` method, you must include the fields with foreign keys in the list of attributes. In the following example, the ``Band`` and ``Manager`` models have a @@ -142,7 +142,7 @@ Exclude Fields ~~~~~~~~~~~~~~ You can explicitly exclude fields from results by using the -``without()`` method. +``without`` method. The following code excludes the ``year`` field from returned ``Band`` objects: @@ -157,7 +157,7 @@ objects: {+odm+} requires the ``_id`` field for various operations, so you *cannot* exclude the ``_id`` field or the ``id`` alias from results. - If you pass ``_id`` or ``id`` to the ``without()`` method, {+odm+} + If you pass ``_id`` or ``id`` to the ``without`` method, {+odm+} ignores it. .. _mongoid-data-sort: @@ -166,7 +166,7 @@ Sort Results ------------ You can specify the order in which {+odm+} returns documents by using the -``order()`` and ``order_by()`` methods. +``order`` and ``order_by`` methods. These methods accept a hash that indicates which fields to order the documents by, and whether to use an ascending or descending order for @@ -189,7 +189,7 @@ shows how to sort on the ``name`` and ``year`` fields: - Example: ``Band.order_by(name: "asc", year: "desc")`` -The ``order()`` method also accepts the following sort specifications: +The ``order`` method also accepts the following sort specifications: - Array of two-element arrays: @@ -211,8 +211,8 @@ The ``order()`` method also accepts the following sort specifications: .. tip:: - Instead of using ``order()`` or ``order_by()``, you can also use the - ``asc()`` and ``desc()`` methods to specify sort orders: + Instead of using ``order`` or ``order_by``, you can also use the + ``asc`` and ``desc`` methods to specify sort orders: .. code-block:: ruby @@ -234,14 +234,14 @@ the previous sorts have been applied. Paginate Results ---------------- -{+odm+} provides the ``limit()``, ``skip()``, and ``batch_size()`` +{+odm+} provides the ``limit``, ``skip``, and ``batch_size`` pagination methods that you can use on ``Criteria`` objects. The following sections describe how to use these operators. Limit Number of Results ~~~~~~~~~~~~~~~~~~~~~~~ -You can use the ``limit()`` method to limit the number of results that +You can use the ``limit`` method to limit the number of results that {+odm+} returns. The following code retrieves a maximum of ``5`` documents: @@ -254,7 +254,7 @@ The following code retrieves a maximum of ``5`` documents: .. note:: - Alternatively, you can use the ``take()`` method to retrieve a + Alternatively, you can use the ``take`` method to retrieve a specified number of documents from the database: .. code-block:: ruby @@ -264,10 +264,10 @@ The following code retrieves a maximum of ``5`` documents: Skip Results ~~~~~~~~~~~~ -You can skip a specified number of results by using the ``skip()`` -method, or its alias ``offset()``. +You can skip a specified number of results by using the ``skip`` +method, or its alias ``offset``. -If you chain a ``limit()`` call to ``skip()``, the limit is applied +If you chain a ``limit`` call to ``skip``, the limit is applied after documents are skipped, as demonstrated in the following example: .. literalinclude:: /includes/interact-data/modify-results.rb @@ -278,7 +278,7 @@ after documents are skipped, as demonstrated in the following example: .. tip:: - When performing pagination, use ``skip()`` on :ref:`sorted results ` + When performing pagination, use ``skip`` on :ref:`sorted results ` to ensure consistent results. The following code skips the first ``3`` documents when returning results: @@ -293,10 +293,10 @@ Generate Batches of Results ~~~~~~~~~~~~~~~~~~~~~~~~~~~ When executing large queries and when iterating over query results by using -an enumerator method such as ``Criteria#each()``, {+odm+} automatically +an enumerator method such as ``Criteria#each``, {+odm+} automatically uses the MongoDB :manual:`getMore ` command to load results in batches. The default batch size is ``1000``, but -you can set a different value by using the ``batch_size()`` method. +you can set a different value by using the ``batch_size`` method. The following code sets the batch size to ``500``: @@ -309,4 +309,8 @@ The following code sets the batch size to ``500``: Additional Information ---------------------- -.. TODO: add links to the bottom of this page +To learn more about constructing queries, see the +:ref:`mongoid-data-specify-query` guide. + +To learn about {+odm+} data modeling, see the +:ref:`mongoid-data-modeling` guides. diff --git a/source/interact-data/nested-attributes.txt b/source/interact-data/nested-attributes.txt index fcd10dd9..e7bc673a 100644 --- a/source/interact-data/nested-attributes.txt +++ b/source/interact-data/nested-attributes.txt @@ -81,9 +81,9 @@ There are multiple ways to update a nested attribute: name>_attributes`` in the value to update the associations. - Use the ``update_attributes`` setter method and specify the attribute names in the value to update the associations. -- Use the ``update()`` method and specify ``_attributes`` in the value to update the associations. -- Use the ``create()`` method and specify ``_attributes`` in the value to create the associations. The following example demonstrates how to create a ``Band`` instance @@ -101,7 +101,7 @@ Creating Nested Documents You can create new nested documents by using the nested attributes feature. When creating a document, omit the ``_id`` field. The following -code uses the ``update()`` method to create a nested ``album`` document +code uses the ``update`` method to create a nested ``album`` document on an existing ``Band`` instance: .. literalinclude:: /includes/data-modeling/nested_attr.rb @@ -118,7 +118,7 @@ Updating Nested Documents You can update existing nested documents by using the nested attributes feature. To instruct {+odm+} to update a nested document by using -attributes, pass the document's ``_id`` value to the ``update()`` +attributes, pass the document's ``_id`` value to the ``update`` method. The following example uses the ``_id`` value of an ``albums`` entry to update the ``year`` field: @@ -137,7 +137,7 @@ Delete Nested Documents ----------------------- You can delete nested documents by specifying the ``_destroy`` -attribute to the ``update()`` method. To enable deletion of nested +attribute to the ``update`` method. To enable deletion of nested document, you must set ``allow_destroy: true`` in the ``accepts_nested_attributes_for`` declaration, as shown in the following code: @@ -187,6 +187,8 @@ Additional Information To learn more about querying, see the :ref:`mongoid-data-specify-query` guide. -.. TODO link to CRUD guide +To learn more about performing CRUD operations, see the +:ref:`mongoid-data-crud` guide. -.. TODO link to associations guide +To learn more about associations, see the :ref:`mongoid-associations` +guide. diff --git a/source/interact-data/query-async.txt b/source/interact-data/query-async.txt index 166b24b4..0891b599 100644 --- a/source/interact-data/query-async.txt +++ b/source/interact-data/query-async.txt @@ -87,6 +87,8 @@ configuration options: Additional Information ---------------------- -.. TODO link to config guide +To learn more about configuring {+odm+} in your application, see the +:ref:`mongoid-app-config` guide. -.. TODO link to crud operations \ No newline at end of file +To learn more about performing CRUD operations, see the +:ref:`mongoid-data-crud` guide. diff --git a/source/interact-data/query-cache.txt b/source/interact-data/query-cache.txt index 2b477dee..d1cefd31 100644 --- a/source/interact-data/query-cache.txt +++ b/source/interact-data/query-cache.txt @@ -42,8 +42,8 @@ Automatic The {+ruby-driver+} provides middleware to automatically enable the query cache for Rack web requests and Active Job job runs. To view instructions on automatically enabling the query cache, see the -:ref:`Query Cache Rack Middleware ` section of -the configuration guide. +:ref:`mongoid-query-cache-rack` section of the Query Cache Middleware +Configuration guide. .. note:: diff --git a/source/interact-data/query-persistence.txt b/source/interact-data/query-persistence.txt index c6a42f77..2578f7a2 100644 --- a/source/interact-data/query-persistence.txt +++ b/source/interact-data/query-persistence.txt @@ -28,7 +28,8 @@ insert, update, and delete operations. To learn more about creating filter criteria, see the :ref:`mongoid-data-specify-query` guide. -.. TODO To learn more about performing CRUD operations, see the :ref:`` guide. +To learn more about performing CRUD operations, see the +:ref:`mongoid-data-crud` guide. Persistence Methods ------------------- diff --git a/source/interact-data/scoping.txt b/source/interact-data/scoping.txt index 49329818..b16c1fb6 100644 --- a/source/interact-data/scoping.txt +++ b/source/interact-data/scoping.txt @@ -101,7 +101,8 @@ You can direct {+odm+} to raise an error when a scope overwrites an existing class method by setting the ``scope_overwrite_exception`` configuration option to ``true``. -.. TODO add link to config options page +To learn more about this setting, see the :ref:`mongoid-app-config` +guide. Default Scopes -------------- @@ -111,10 +112,10 @@ criteria to most queries. By defining a default scope, you specify these criteria as the default for any queries that use the model. Default scopes return ``Criteria`` objects. -To create a default scope, you must define the ``default_scope()`` method +To create a default scope, you must define the ``default_scope`` method on your model class. -The following code defines the ``default_scope()`` method on the ``Band`` +The following code defines the ``default_scope`` method on the ``Band`` model to only retrieve documents in which the ``active`` field value is ``true``: .. literalinclude:: /includes/interact-data/scoping.rb @@ -179,7 +180,7 @@ Suppose you create a ``Label`` model that contains an association to a ``Band`` in which the value of ``active`` is ``true``. When you update the ``active`` field to ``false``, {+odm+} still loads it despite the default scope. To view the documents in the association with the scope -applied, you must call the ``reload()`` operator. +applied, you must call the ``reload`` operator. The following code demonstrates this sequence: @@ -195,7 +196,7 @@ or and nor Query Behavior {+odm+} treats the criteria in a default scope the same way as any other query conditions. This can lead to surprising behavior when using the -``or()`` and ``nor()`` methods. +``or`` and ``nor`` methods. The following examples demonstrate how {+odm+} interprets queries on models with a default scope: @@ -224,7 +225,7 @@ You can direct {+odm+} to not apply the default scope by using the Override Default Scope at Runtime --------------------------------- -You can use the ``with_scope()`` method to change the default scope in a +You can use the ``with_scope`` method to change the default scope in a block at runtime. The following model defines the *named* scope ``mexican``: @@ -236,7 +237,7 @@ The following model defines the *named* scope ``mexican``: :dedent: :emphasize-lines: 7 -You can use the ``with_scope()`` method to set the ``mexican`` named +You can use the ``with_scope`` method to set the ``mexican`` named scope as the default scope at runtime, as shown in the following code: .. literalinclude:: /includes/interact-data/scoping.rb @@ -262,4 +263,5 @@ the following example: Additional Information ---------------------- -.. TODO add info links \ No newline at end of file +To learn more about customizing your {+odm+} models, see the +:ref:`mongoid-data-modeling` guides. diff --git a/source/interact-data/specify-query.txt b/source/interact-data/specify-query.txt index 6e70e805..6b77b622 100644 --- a/source/interact-data/specify-query.txt +++ b/source/interact-data/specify-query.txt @@ -75,10 +75,10 @@ following example demonstrates the return type for a simple query: # Returns matching documents # => [{"_id":"...","name":"Deftones"}] -You can use methods such as ``first()`` and ``last()`` to return +You can use methods such as ``first`` and ``last`` to return individual documents. You can also iterate a ``Criteria`` object by using -methods such as ``each()`` or ``map()`` to retrieve documents from the -server. You can use ``to_json()`` to convert a ``Criteria`` object to +methods such as ``each`` or ``map`` to retrieve documents from the +server. You can use ``to_json`` to convert a ``Criteria`` object to JSON. .. tip:: Chaining methods @@ -175,8 +175,6 @@ types of values. Defined Fields ~~~~~~~~~~~~~~ -.. TODO add link to Fields page - To query on a field, the field does not need to be in the the model class definition. However, if a field is defined in the model class, {+odm+} coerces query values to match the defined field @@ -193,6 +191,9 @@ to ``2020`` when performing the query: :language: ruby :dedent: +To learn more about defining fields in {+odm+}, see the +:ref:`mongoid-field-types` guide. + Raw Values ~~~~~~~~~~ @@ -211,10 +212,9 @@ the ``Mongoid::RawValue`` class, as shown in the following code: Field Aliases ~~~~~~~~~~~~~ -.. TODO update links - -Queries follow the :ref:`storage field names ` -and :ref:`field aliases ` that you might have set in your +Queries follow the :ref:`storage field +names ` and :ref:`field aliases +` that you might have set in your model class definition. The ``id`` and ``_id`` fields are aliases, so you can use either field @@ -261,13 +261,13 @@ Logical Operations {+odm+} supports the following logical operations on ``Criteria`` objects: -- ``and()`` -- ``or()`` -- ``nor()`` -- ``not()`` +- ``and`` +- ``or`` +- ``nor`` +- ``not`` These methods take one or more hashes of conditions or another -``Criteria`` object as their arguments. The ``not()`` operation has an +``Criteria`` object as their arguments. The ``not`` operation has an argument-free version. The following code demonstrates how to use the logical operations in @@ -312,10 +312,10 @@ following code: :language: ruby :dedent: -The ``any_of()``, ``none_of()``, ``nor()``, and ``not()`` operations +The ``any_of``, ``none_of``, ``nor``, and ``not`` operations behave similarly. -When you use ``and()``, ``or()``, and ``nor()`` logical operators, they +When you use ``and``, ``or``, and ``nor`` logical operators, they operate on the criteria built up to that point: .. literalinclude:: /includes/interact-data/query.rb @@ -324,15 +324,15 @@ operate on the criteria built up to that point: :language: ruby :dedent: -not() Behavior -~~~~~~~~~~~~~~ +not Behavior +~~~~~~~~~~~~ -You can use the ``not()`` method without arguments, in which case it -negates the next condition that is specified. The ``not()`` method can +You can use the ``not`` method without arguments, in which case it +negates the next condition that is specified. The ``not`` method can be called with one or more hash conditions or ``Criteria`` objects, which are all negated and added to the criteria. -The following examples demonstrate the behavior of ``not()``: +The following examples demonstrate the behavior of ``not``: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-not-logical @@ -351,7 +351,7 @@ The following examples demonstrate the behavior of ``not()``: :language: ruby :dedent: -Similarly to ``and()``, the ``not()`` operation negates individual +Similarly to ``and``, the ``not`` operation negates individual conditions for simple field criteria. For complex conditions and when a field already has a condition defined on it, {+odm+} emulates ``$not`` by using an ``{'$and' => [{'$nor' => ...}]}`` construct, because MongoDB @@ -364,7 +364,7 @@ globally: :language: ruby :dedent: -If you are using ``not()`` with arrays or regular expressions, view the +If you are using ``not`` with arrays or regular expressions, view the limitations of ``$not`` in the :manual:`{+server-manual+} `. @@ -373,7 +373,7 @@ Incremental Query Construction By default, when you add conditions to a query, {+odm+} considers each condition complete and independent from any other conditions -present in the query. For example, calling ``in()`` twice adds two separate +present in the query. For example, calling ``in`` twice adds two separate ``$in`` conditions: .. literalinclude:: /includes/interact-data/query.rb @@ -408,7 +408,7 @@ Merge Strategies operator value. The following code demonstrates how the merge strategies produce -criteria by using ``in()`` as the example operator: +criteria by using ``in`` as the example operator: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-in-merge @@ -416,7 +416,7 @@ criteria by using ``in()`` as the example operator: :language: ruby :dedent: -The strategy is requested by calling ``override()``, ``intersect()`` or ``union()`` +The strategy is requested by calling ``override``, ``intersect`` or ``union`` on a ``Criteria`` instance. The requested strategy applies to the next condition method called on the query. If the next condition method called does not support merge strategies, the strategy is reset, as shown in the following @@ -428,8 +428,8 @@ example: :language: ruby :dedent: -Because ``ne()`` does not support merge strategies, the ``union`` strategy -is ignored and reset. Then, when ``in()`` is invoked the second time, +Because ``ne`` does not support merge strategies, the ``union`` strategy +is ignored and reset. Then, when ``in`` is invoked the second time, there is no active strategy. .. warning:: @@ -444,9 +444,9 @@ Supported Operator Methods The following operator methods support merge strategies: -- ``all()`` -- ``in()`` -- ``nin()`` +- ``all`` +- ``in`` +- ``nin`` The set of methods might be expanded in future releases of {+odm+}. To ensure future compatibility, invoke a strategy method only when the next method call @@ -455,7 +455,7 @@ is an operator that supports merge strategies. Merge strategies are applied only when conditions are added through the designated methods. In the following example, the merge strategy is not applied because the second condition is added as -``where()``, not by using ``in()``: +``where``, not by using ``in``: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-merge-where @@ -471,7 +471,7 @@ value type. {+odm+} expands ``Array``-compatible types, such as a ``Range``, when they are used with these operator methods. The following example demonstrates how you can pass a ``Range`` object -as the query value when using the ``in()`` method: +as the query value when using the ``in`` method: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-range-query @@ -490,12 +490,12 @@ as the shown in the following example: Element Match ------------- -You can use the ``elem_match()`` method to match documents that contain +You can use the ``elem_match`` method to match documents that contain an array field with at least one element that matches all the specified query criteria. The following example creates a sample document that contains an array -field. Then, it uses the ``elem_match()`` method to match documents in +field. Then, it uses the ``elem_match`` method to match documents in which the ``tour`` array field contains an entry in which the ``city`` value is ``'London'``: @@ -508,7 +508,7 @@ value is ``'London'``: Associations ~~~~~~~~~~~~ -You can use the ``elem_match()`` method to match embedded associations. +You can use the ``elem_match`` method to match embedded associations. This example uses the following models that define an embedded association between ``Band`` and ``Tour``: @@ -520,7 +520,7 @@ association between ``Band`` and ``Tour``: :dedent: The following code creates a ``Band`` object and embedded ``Tour`` -objects, then uses the ``elem_match()`` method to query on the ``city`` +objects, then uses the ``elem_match`` method to query on the ``city`` field: .. literalinclude:: /includes/interact-data/query.rb @@ -531,13 +531,13 @@ field: .. note:: - You cannot use ``elem_match()`` on non-embedded associations because + You cannot use ``elem_match`` on non-embedded associations because MongoDB does not perform a join operation on the collections. If you perform this query, the conditions are added to the collection that is the source of the non-embedded association rather than the collection of the association. -You can use ``elem_match()`` to query recursively embedded associations, +You can use ``elem_match`` to query recursively embedded associations, as shown in the following example: .. literalinclude:: /includes/interact-data/query.rb @@ -546,13 +546,16 @@ as shown in the following example: :language: ruby :dedent: +To learn more about associations, see the :ref:`mongoid-associations` +guide. + Querying by _id Value --------------------- -{+odm+} provides the ``find()`` method, which allows you to query +{+odm+} provides the ``find`` method, which allows you to query documents by their ``_id`` values. -The following example uses the ``find()`` method to match a document +The following example uses the ``find`` method to match a document with the specified ``_id`` field value: .. code-block:: ruby @@ -561,22 +564,22 @@ with the specified ``_id`` field value: .. note:: Type Conversion - When you pass an ID value to the ``find()`` method, the method + When you pass an ID value to the ``find`` method, the method converts it to the data type declared for the ``_id`` field in the model. By default, the ``_id`` field is defined as a ``BSON::ObjectId`` type. The preceding example is equivalent to the following code, which - passes an ``BSON::ObjectId`` instance as the argument to ``find()``: + passes an ``BSON::ObjectId`` instance as the argument to ``find``: .. code-block:: ruby Band.find(BSON::ObjectId.from_string('5f0e41d92c97a64a26aabd10')) If you use the {+ruby-driver+} to query on the ``_id`` field, - ``find()`` does not internally perform the type conversion. + ``find`` does not internally perform the type conversion. -The ``find()`` method accepts multiple arguments, or an array of arguments. +The ``find`` method accepts multiple arguments, or an array of arguments. {+odm+} interprets each argument or array element as an ``_id`` value, and returns documents with all the specified ``_id`` values in an array, as shown in the following example: @@ -587,7 +590,7 @@ values in an array, as shown in the following example: :language: ruby :dedent: -The ``find()`` method exhibits the following behavior: +The ``find`` method exhibits the following behavior: - If you provide the same ``_id`` value more than once, {+odm+} returns only one document, if one exists. @@ -601,27 +604,27 @@ The ``find()`` method exhibits the following behavior: option. If you set the ``raise_not_found_error`` option to ``true``, - ``find()`` raises a ``Mongoid::Errors::DocumentNotFound`` error if any + ``find`` raises a ``Mongoid::Errors::DocumentNotFound`` error if any of the ``_id`` values are not found. If you set the ``raise_not_found_error`` option to ``false`` and query - for a single ``_id`` value, ``find()`` returns ``nil`` if {+odm+} does + for a single ``_id`` value, ``find`` returns ``nil`` if {+odm+} does not match a document. If you pass multiple ``_id`` values and some or all are not matched, the return value is an array of any documents that match, or an empty array if no documents match. -find() Variations ------------------ +find Variations +--------------- -This section describes methods that are similar to the ``find()`` method +This section describes methods that are similar to the ``find`` method described in the preceding section. -You can use the ``find_by()`` method to retrieve documents based on the +You can use the ``find_by`` method to retrieve documents based on the provided criteria. If no documents are found, it raises an error or returns ``nil`` depending on how you set the ``raise_not_found_error`` configuration option. -The following code demonstrates how to use the ``find_by()`` method: +The following code demonstrates how to use the ``find_by`` method: .. literalinclude:: /includes/interact-data/query.rb :start-after: start-query-findby @@ -629,11 +632,11 @@ The following code demonstrates how to use the ``find_by()`` method: :language: ruby :dedent: -You can use the ``find_or_create_by()`` method to retrieve documents +You can use the ``find_or_create_by`` method to retrieve documents based on the provided criteria. If no documents are found, it creates and returns an instance that is saved to MongoDB. -The following code demonstrates how to use the ``find_or_create_by()`` +The following code demonstrates how to use the ``find_or_create_by`` method: .. literalinclude:: /includes/interact-data/query.rb @@ -642,12 +645,14 @@ method: :language: ruby :dedent: -You can use the ``find_or_initialize_by()`` method to retrieve documents +You can use the ``find_or_initialize_by`` method to retrieve documents based on the provided criteria. If no documents are found, it returns a new one, without persisting it to MongoDB. Use the same syntax for -``find_or_initialize_by()`` as you do for the ``find_or_create_by()`` +``find_or_initialize_by`` as you do for the ``find_or_create_by`` method. +.. _mongoid-query-regex: + Regular Expressions ------------------- @@ -678,6 +683,8 @@ You can also perform queries by using Perl Compatible Regular Expression :language: ruby :dedent: +.. _mongoid-query-field-type-conversions: + Field Type Query Conversions ---------------------------- @@ -717,11 +724,10 @@ behavior, as shown in the following example: In the preceding example, the following conversions apply: -.. TODO add link to time zone configuration - - When using a ``Date`` value to query the ``Time``-valued ``last_commented`` field, {+odm+} interprets the date to be in local - time and applies the :ref:`configured time zone <>`. + time and applies the :ref:`configured time zone + `. - When querying on the ``last_purchased`` field, which has no explicit type, the date is used unmodified in the constructed query. @@ -739,11 +745,11 @@ This section describes more query methods that you can use in {+odm+}. Count Documents ~~~~~~~~~~~~~~~ -You can use the ``count()`` and ``estimated_count()`` methods to count +You can use the ``count`` and ``estimated_count`` methods to count the number of documents in a collection. You can count the number of documents that match filter criteria by -using the ``count()`` method: +using the ``count`` method: .. code-block:: ruby @@ -753,23 +759,23 @@ using the ``count()`` method: # Counts documents that match criteria Band.where(country: 'England').count -.. tip:: length() and size() Methods +.. tip:: length and size Methods - You can also use the ``length()`` or ``size()`` method to count documents. + You can also use the ``length`` or ``size`` method to count documents. These methods cache subsequent calls to the database, which might produce performance improvements. You can get an approximate number of documents in the collection from -the collection metadata by using the ``estimated_count()`` method: +the collection metadata by using the ``estimated_count`` method: .. code-block:: ruby Band.estimated_count -The ``estimated_count()`` method does not accept query conditions, +The ``estimated_count`` method does not accept query conditions, including conditions set by a :ref:`scope ` on the model. If you are calling this method on a model that has a -default scope, you must first call the ``unscoped()`` method to +default scope, you must first call the ``unscoped`` method to disable the scope. Ordinal Methods @@ -778,39 +784,39 @@ Ordinal Methods The methods described in the following list allow you to select a specific result from the list of returned documents based on its position. -- ``first()``: Returns the first matching document. You can get the +- ``first``: Returns the first matching document. You can get the first ``n`` documents by passing an integer-valued parameter. This method automatically uses a sort on the ``_id`` field. *See lines 1-8 in the following code for examples.* -- ``last()``: Returns the last matching document. You can get the +- ``last``: Returns the last matching document. You can get the last ``n`` documents by passing an integer-valued parameter. This method automatically uses a sort on the ``_id`` field. *See line 11 in the following code for an example.* -- ``first_or_create()``: Returns the first matching document. If no +- ``first_or_create``: Returns the first matching document. If no document matches, creates and returns a newly saved one. -- ``first_or_initialize()``: Returns the first matching document. If no +- ``first_or_initialize``: Returns the first matching document. If no document matches, returns a new one. -- ``second()``: Returns the second matching document. Automatically uses +- ``second``: Returns the second matching document. Automatically uses a sort on the ``_id`` field. -- ``third()``: Returns the third matching document. Automatically uses +- ``third``: Returns the third matching document. Automatically uses a sort on the ``_id`` field. -- ``fourth()``: Returns the fourth matching document. Automatically uses +- ``fourth``: Returns the fourth matching document. Automatically uses a sort on the ``_id`` field. -- ``fifth()``: Returns the fifth matching document. Automatically uses +- ``fifth``: Returns the fifth matching document. Automatically uses a sort on the ``_id`` field. -- ``second_to_last()``: Returns the second-to-last matching document. +- ``second_to_last``: Returns the second-to-last matching document. Automatically uses a sort on the ``_id`` field. *See line 14 in the following code for an example.* -- ``third_to_last()``: Returns the third-to-last matching document. +- ``third_to_last``: Returns the third-to-last matching document. Automatically uses a sort on the ``_id`` field. The following code demonstrates how to use some methods described @@ -828,8 +834,8 @@ in the preceding list: Each method described in this section has a variation that is suffixed with ``!`` that returns an error if {+odm+} doesn't match any documents. For example, to implement error handling in your - application when your query returns no results, use the ``first!()`` - method instead of ``first()``. + application when your query returns no results, use the ``first!`` + method instead of ``first``. Survey Field Values ~~~~~~~~~~~~~~~~~~~ @@ -837,24 +843,25 @@ Survey Field Values To inspect the values of specified fields of documents in a collection, you can use the following methods: -- ``distinct()``: Gets a list of distinct values for a single field. +- ``distinct``: Gets a list of distinct values for a single field. *See lines 1-7 in the following code for examples.* -- ``pick()``: Gets the values from one document for the provided fields. +- ``pick``: Gets the values from one document for the provided fields. Returns ``nil`` for unset fields and for non-existent fields. *See line 10 in the following code for an example.* -- ``pluck()``: Gets all values for the provided field. Returns ``nil`` +- ``pluck``: Gets all values for the provided field. Returns ``nil`` for unset fields and for non-existent fields. *See line 13 in the following code for an example.* -- ``tally()``: Gets a mapping of values to counts for the specified +- ``tally``: Gets a mapping of values to counts for the specified field. *See line 16 in the following code for an example.* The preceding methods accept field names referenced by using dot notation, which allows you to reference fields in embedded associations. -They also respect :ref:`field aliases `, including -those defined in embedded documents. +They also respect :ref:`field aliases +`, including those defined in embedded +documents. The following code demonstrates how to use these methods: @@ -871,7 +878,7 @@ Miscellaneous The following list describes {+odm+} methods that do not fit into another category: -- ``each()``: Iterates over all matching documents. +- ``each``: Iterates over all matching documents. .. code-block:: ruby @@ -880,7 +887,7 @@ category: p band.name end -- ``exists?()``: Determines if any matching documents exist, returning +- ``exists?``: Determines if any matching documents exist, returning ``true`` if at least one matching document is found. .. code-block:: ruby diff --git a/source/interact-data/text-search.txt b/source/interact-data/text-search.txt index a777bf0d..c39e90c1 100644 --- a/source/interact-data/text-search.txt +++ b/source/interact-data/text-search.txt @@ -49,8 +49,6 @@ The following sections describe how to perform each of these actions. Define a Text Index on Your Model --------------------------------- -.. TODO link to indexes page - Use the ``index`` macro to specify the text index in your model definition. The following code creates a ``Dish`` model class that includes a text index on the ``description`` field: @@ -80,6 +78,9 @@ Rake task to create the index based on your model specification: bundle exec rake db:mongoid:create_indexes +To learn more about using indexes with {+odm+}, see the +:ref:`mongoid-indexes` guide. + Perform Text Searches --------------------- @@ -210,4 +211,5 @@ Additional Information To learn more about constructing query filters, see :ref:`mongoid-data-specify-query`. -.. TODO link to CRUD guide +To learn more about performing CRUD operations, see the +:ref:`mongoid-data-crud` guide. diff --git a/source/interact-data/transaction.txt b/source/interact-data/transaction.txt index 04cef370..71ec9ae2 100644 --- a/source/interact-data/transaction.txt +++ b/source/interact-data/transaction.txt @@ -60,10 +60,10 @@ You can use the High-Level Transaction API to internally manage the lifecycle of your transaction. This API either commits your transaction or ends it and incorporates error handling logic. -You can start a transaction by calling the ``transaction()`` method on +You can start a transaction by calling the ``transaction`` method on an instance of a model, on the model class, or on a ``{+odm+}`` module. -When you call the ``transaction()`` method, {+odm+} performs the +When you call the ``transaction`` method, {+odm+} performs the following tasks: 1. Creates a session on the client. @@ -124,7 +124,7 @@ and demonstrates how operations are run based on the origin client: .. note:: - When you call the ``transaction()`` method on the ``{+odm+}`` module, + When you call the ``transaction`` method on the ``{+odm+}`` module, {+odm+} creates the transaction by using the ``:default`` client. Ending Transactions @@ -168,7 +168,8 @@ created, saved, or deleted inside a transaction, if the transaction was unsuccessful and changes were rolled back. {+odm+} never triggers ``after_rollback`` outside of a transaction. -.. TODO link to callbacks guide. +To learn more about callbacks, see the :ref:`mongoid-modeling-callbacks` +guide. .. _mongoid-txn-low-level: @@ -177,12 +178,12 @@ Low-Level Transaction API When using the low-level API, you must create a session before starting a transaction. You can create a session by calling the -``with_session()`` method on a model class or an instance of a model. +``with_session`` method on a model class or an instance of a model. -Then, you can start a transaction by calling the ``start_transaction()`` +Then, you can start a transaction by calling the ``start_transaction`` method on a session. When using this API, you must manually commit or -end the transaction. You can use the ``commit_transaction()`` and -``abort_transaction()`` methods on the session instance to manage the +end the transaction. You can use the ``commit_transaction`` and +``abort_transaction`` methods on the session instance to manage the transaction lifecycle. Example @@ -225,7 +226,7 @@ Options You can specify a read concern, write concern or read preference when starting a transaction by passing options to the -``start_transaction()`` method: +``start_transaction`` method: .. code-block:: ruby @@ -236,7 +237,7 @@ preference when starting a transaction by passing options to the ) To learn more about the available transaction options, see -:ruby-api:`start_transaction() +:ruby-api:`start_transaction ` in the {+ruby-driver+} API documentation. @@ -247,7 +248,7 @@ To perform operations within a transaction, operations must use the same client that the session was initiated on. By default, all operations are performed by using the default client. -To explicitly use a different client, use the ``with()`` method: +To explicitly use a different client, use the ``with`` method: .. literalinclude:: /includes/interact-data/transaction.rb :start-after: start-other-client @@ -261,7 +262,7 @@ Session API ----------- You can use sessions in {+odm+} in a similar way that you can -perform a transaction. You can call the ``with_session()`` method on a +perform a transaction. You can call the ``with_session`` method on a model class or on an instance of a model and perform some operations in a block. All operations in the block will be performed in the context of single session. @@ -270,9 +271,9 @@ The following limitations apply when using sessions: - You cannot share a session across threads. Sessions are not thread-safe. -- You cannot nest sessions. For example, you cannot call the ``with_session()`` +- You cannot nest sessions. For example, you cannot call the ``with_session`` method on a model class within the block passed to - the ``with_session()`` method on another model class. The following + the ``with_session`` method on another model class. The following code demonstrates a nested session that results in an error: .. code-block:: ruby @@ -287,13 +288,13 @@ The following limitations apply when using sessions: - All model classes and instances used within the session block must use the same driver client. For example, if you specify different a different client for a model used in the block than those of the - model or instance that you called ``with_session()`` on, {+odm+} returns + model or instance that you called ``with_session`` on, {+odm+} returns an error. Examples ~~~~~~~~ -You can use the ``with_session()`` method on a model class and pass it session +You can use the ``with_session`` method on a model class and pass it session options to perform a block of operations in the context of a session. The following code enables the ``causal_consistency`` option to @@ -311,7 +312,7 @@ To learn more about the available session options, see the ` in the {+ruby-driver+} API documentation. -Alternatively, you can use the ``with_session()`` method on an instance of a +Alternatively, you can use the ``with_session`` method on an instance of a model and pass it session options to perform a block of operations in the context of a session. @@ -331,4 +332,5 @@ Additional Information To learn more about transactions, see :manual:`Transactions ` in the {+server-manual+}. -.. TODO To learn more about performing CRUD operations, see the :ref:`` guide. +To learn more about performing CRUD operations, see the +:ref:`mongoid-data-crud` guide. diff --git a/source/legacy-files/upgrading.txt b/source/legacy-files/upgrading.txt index 3b5b39bc..ef6bdd3b 100644 --- a/source/legacy-files/upgrading.txt +++ b/source/legacy-files/upgrading.txt @@ -51,7 +51,7 @@ 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. -- *Upgrade Ruby and Rails:* See `"Upgrading {+ror+}" `_ +- *Upgrade Ruby and Rails:* See `"Upgrading {+ror+}" <{+active-record-docs+}/upgrading_ruby_on_rails.html>`_ for more information diff --git a/source/quick-start-rails.txt b/source/quick-start-rails.txt index f2db9fd5..17fb1571 100644 --- a/source/quick-start-rails.txt +++ b/source/quick-start-rails.txt @@ -35,7 +35,7 @@ To learn how to integrate {+odm+} into an existing application, see the `. {+odm+} is an Object-Document Mapper (ODM) framework for MongoDB in -Ruby. By using {+odm+}, you can easily interact with your data and +{+language+}. By using {+odm+}, you can easily interact with your data and create flexible data models. {+ror+} is a web application framework for @@ -56,9 +56,6 @@ Follow the steps in this guide to create a sample {+odm+} web application that connects to a MongoDB deployment. .. tip:: Other Framework Tutorials - - If you prefer to use Rails 6 to build your application, see the - :ref:`mongoid-getting-started-rails-6` guide. If you prefer to use Sinatra as your web framework, see the :ref:`mongoid-quick-start-sinatra` guide. diff --git a/source/quick-start-rails/download-and-install.txt b/source/quick-start-rails/download-and-install.txt index cf7ae849..009325dd 100644 --- a/source/quick-start-rails/download-and-install.txt +++ b/source/quick-start-rails/download-and-install.txt @@ -17,10 +17,10 @@ Prerequisites To create the Quick Start application by using {+ror+} 8, you need the following software installed in your development environment: -- `{+language+}. `__ +- `{+language+} language. `__ Rails requires {+language+} v3.1.0 or later. Use the latest version to prevent version conflicts. -- `RubyGems package manager. `__ +- :rubygems:`RubyGems package manager. ` - A terminal app and shell. For MacOS users, use Terminal or a similar app. For Windows users, use PowerShell. diff --git a/source/quick-start-rails/next-steps.txt b/source/quick-start-rails/next-steps.txt index 1869ddf1..aadc3e60 100644 --- a/source/quick-start-rails/next-steps.txt +++ b/source/quick-start-rails/next-steps.txt @@ -21,11 +21,10 @@ the sample data, and render retrieved results. .. `mongoid-quickstart <>`__ .. GitHub repository. -.. TODO Learn more about {+odm+} features from the following resources: +Learn more about {+odm+} features from the following sections: -.. - :ref:`mongoid-fundamentals-connection`: Learn how to configure your MongoDB -.. connection. -.. -.. - :ref:`mongoid-usage-examples`: See code examples of frequently used MongoDB -.. operations. +- :ref:`mongoid-data-modeling`: Learn how to customize your {+odm+} + models. +- :ref:`mongoid-interact-data`: Learn how to interact with your MongoDB + data by using {+odm+} models. diff --git a/source/quick-start-rails/view-data.txt b/source/quick-start-rails/view-data.txt index 81f1355c..3e06b140 100644 --- a/source/quick-start-rails/view-data.txt +++ b/source/quick-start-rails/view-data.txt @@ -32,7 +32,7 @@ View MongoDB Data The ``app/controllers/restaurants_controller.rb`` file contains methods that specify how your app handles different - requests. Replace the ``index()`` method body with the following code: + requests. Replace the ``index`` method body with the following code: .. code-block:: ruby diff --git a/source/quick-start-sinatra.txt b/source/quick-start-sinatra.txt index a5fb7bc7..d9d88d84 100644 --- a/source/quick-start-sinatra.txt +++ b/source/quick-start-sinatra.txt @@ -34,7 +34,7 @@ To learn how to integrate {+odm+} into an existing application, see the `. {+odm+} is an Object-Document Mapper (ODM) framework for MongoDB in -Ruby. By using {+odm+}, you can easily interact with your data and +{+language+}. By using {+odm+}, you can easily interact with your data and create flexible data models. Sinatra is a domain-specific language (DSL) for creating web diff --git a/source/quick-start-sinatra/download-and-install.txt b/source/quick-start-sinatra/download-and-install.txt index b0a92bbf..fa762905 100644 --- a/source/quick-start-sinatra/download-and-install.txt +++ b/source/quick-start-sinatra/download-and-install.txt @@ -17,8 +17,8 @@ Prerequisites To create the Quick Start application by using Sinatra, you need the following software installed in your development environment: -- `{+language+}. `__ -- `RubyGems package manager. `__ +- `{+language+} language. `__ +- :rubygems:`RubyGems package manager. ` - A terminal app and shell. For MacOS users, use Terminal or a similar app. For Windows users, use PowerShell. diff --git a/source/quick-start-sinatra/next-steps.txt b/source/quick-start-sinatra/next-steps.txt index d2c03d89..f27b0c2f 100644 --- a/source/quick-start-sinatra/next-steps.txt +++ b/source/quick-start-sinatra/next-steps.txt @@ -21,11 +21,10 @@ the sample data, and render retrieved results. .. `mongoid-quickstart <>`__ .. GitHub repository. -.. TODO Learn more about {+odm+} features from the following resources: +Learn more about {+odm+} features from the following sections: -.. - :ref:`mongoid-fundamentals-connection`: Learn how to configure your MongoDB -.. connection. -.. -.. - :ref:`mongoid-usage-examples`: See code examples of frequently used MongoDB -.. operations. +- :ref:`mongoid-data-modeling`: Learn how to customize your {+odm+} + models. +- :ref:`mongoid-interact-data`: Learn how to interact with your MongoDB + data by using {+odm+} models. diff --git a/source/quick-start-sinatra/view-data.txt b/source/quick-start-sinatra/view-data.txt index 00171ffc..3925552a 100644 --- a/source/quick-start-sinatra/view-data.txt +++ b/source/quick-start-sinatra/view-data.txt @@ -103,7 +103,7 @@ View MongoDB Data .. step:: (Optional) View your results as JSON documents Instead of generating a view to render your results, you can - use the ``to_json()`` method to display your results in JSON + use the ``to_json`` method to display your results in JSON format. Replace the ``list_restaurants`` route in the ``app.rb`` file with diff --git a/source/security/encryption.txt b/source/security/encryption.txt index 904affc0..641bf12a 100644 --- a/source/security/encryption.txt +++ b/source/security/encryption.txt @@ -110,7 +110,7 @@ mongocryptd (Driver v2.18 or earlier) If you are using {+ruby-driver+} version 2.18 or earlier, you must use ``mongocryptd`` instead of the {+shared-library+}. ``mongocryptd`` comes -pre-packaged with enterprise builds of MongoDB Server. For instructions on how to +pre-packaged with enterprise builds of {+mdb-server+}. For instructions on how to install and configure ``mongocryptd``, see the :manual:`Install mongocryptd guide ` in the MongoDB {+server-manual+}. @@ -118,7 +118,7 @@ install and configure ``mongocryptd``, see the :manual:`Install mongocryptd guid ffi ~~~ -{+odm+} uses the `ffi gem `__ to call functions from +{+odm+} uses the :github:`ffi gem ` to call functions from ``libmongocrypt``. Add the gem to your ``Gemfile`` by running the following command in your shell: @@ -330,4 +330,5 @@ To learn more about using CSFLE with the {+ruby-driver+}, see the ` guide in the {+ruby-driver+} documentation. -.. TODO: Add link to mongoid configuration page for encryption settings when available. \ No newline at end of file +To learn more about configuring {+odm+} in your application, see the +:ref:`mongoid-app-config` guide. From e01eb51e30d4b3b868ac76a6e6f516f654207ce1 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 27 Jan 2025 16:53:02 -0500 Subject: [PATCH 112/113] add index section --- source/includes/fact-environments.rst | 7 +++++++ source/index.txt | 8 ++++++++ 2 files changed, 15 insertions(+) create mode 100644 source/includes/fact-environments.rst diff --git a/source/includes/fact-environments.rst b/source/includes/fact-environments.rst new file mode 100644 index 00000000..737ab901 --- /dev/null +++ b/source/includes/fact-environments.rst @@ -0,0 +1,7 @@ +- `MongoDB Atlas + `__: the fully + managed service for MongoDB deployments in the cloud +- :manual:`MongoDB Enterprise `: the + subscription-based, self-managed version of MongoDB +- :manual:`MongoDB Community `: the + source-available, free-to-use, and self-managed version of MongoDB diff --git a/source/index.txt b/source/index.txt index 3ed8ed5d..d2b744d6 100644 --- a/source/index.txt +++ b/source/index.txt @@ -34,6 +34,14 @@ a MongoDB database. Install {+odm+} by adding it to your project's ``Gemfile`` or set up a runnable project by following one of the Quick Start guides. +Connect to a Compatible MongoDB Deployment +------------------------------------------ + +You can use {+odm+} to connect to MongoDB deployments running on one of +the following hosted services or editions: + +.. include:: /includes/fact-environments.rst + Quick Start ----------- From 7de774a779ae54a407f9f3d097cac244e83da7f4 Mon Sep 17 00:00:00 2001 From: rustagir Date: Tue, 28 Jan 2025 09:17:41 -0500 Subject: [PATCH 113/113] change vs in redirects --- config/redirects | 120 +++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/config/redirects b/config/redirects index a0b5418b..6be7b119 100644 --- a/config/redirects +++ b/config/redirects @@ -1,6 +1,6 @@ define: prefix docs/mongoid define: base https://www.mongodb.com/${prefix} -define: versions v9.0 master +define: versions v8.0 v8.1 v9.0 master symlink: current -> v9.0 symlink: upcoming -> master @@ -9,64 +9,64 @@ raw: ${prefix}/ -> ${base}/current/ raw: ${prefix}/stable -> ${base}/current/ # Redirects for standardized (new) page URLs in old docs -[*-v9.0]: ${prefix}/${version}/compatibility/ -> ${base}/${version}/reference/compatibility/ -[*-v9.0]: ${prefix}/${version}/configuration/ -> ${base}/${version}/reference/configuration/ -[*-v9.0]: ${prefix}/${version}/integrations-tools/rails-integration/ -> ${base}/${version}/reference/rails-integration/ -[*-v9.0]: ${prefix}/${version}/quick-start-sinatra/ -> ${base}/${version}/tutorials/getting-started-sinatra/ -[*-v9.0]: ${prefix}/${version}/quick-start-rails/ -> ${base}/${version}/tutorials/getting-started-rails7/ -[*-v9.0]: ${prefix}/${version}/data-modeling/documents/ -> ${base}/${version}/tutorials/documents/ -[*-v9.0]: ${prefix}/${version}/security/encryption/ -> ${base}/${version}/tutorials/automatic-encryption/ -[*-v9.0]: ${prefix}/${version}/data-modeling/field-types/ -> ${base}/${version}/reference/fields/ -[*-v9.0]: ${prefix}/${version}/data-modeling/inheritance/ -> ${base}/${version}/reference/inheritance/ -[*-v9.0]: ${prefix}/${version}/data-modeling/associations/ -> ${base}/${version}/reference/associations/ -[*-v9.0]: ${prefix}/${version}/data-modeling/validation/ -> ${base}/${version}/reference/validation/ -[*-v9.0]: ${prefix}/${version}/configuration/collection-config/ -> ${base}/${version}/reference/collection-configuration/ -[*-v9.0]: ${prefix}/${version}/data-modeling/indexes/ -> ${base}/${version}/reference/indexes/ -[*-v9.0]: ${prefix}/${version}/configuration/sharding/ -> ${base}/${version}/reference/sharding/ -[*-v9.0]: ${prefix}/${version}/interact-data/crud/ -> ${base}/${version}/reference/crud/ -[*-v9.0]: ${prefix}/${version}/interact-data/specify-query/ -> ${base}/${version}/reference/queries/ -[*-v9.0]: ${prefix}/${version}/interact-data/text-search/ -> ${base}/${version}/reference/text-search/ -[*-v9.0]: ${prefix}/${version}/interact-data/aggregation/ -> ${base}/${version}/reference/aggregation/ -[*-v9.0]: ${prefix}/${version}/configuration/persistence-config/ -> ${base}/${version}/reference/persistence-configuration/ -[*-v9.0]: ${prefix}/${version}/interact-data/nested-attributes/ -> ${base}/${version}/reference/nested-attributes/ -[*-v9.0]: ${prefix}/${version}/data-modeling/callbacks/ -> ${base}/${version}/reference/callbacks/ -[*-v9.0]: ${prefix}/${version}/interact-data/transaction/ -> ${base}/${version}/reference/transactions/ -[*-v9.0]: ${prefix}/${version}/whats-new/ -> ${base}/${version}/release-notes/mongoid-9.0/ -[*-v9.0]: ${prefix}/${version}/code-documentation/ -> ${base}/${version}/contributing/code-documentation/ -[*-v9.0]: ${prefix}/${version}/issues-and-help/ -> ${base}/${version}/contributing/contributing-guidelines/ -[*-v9.0]: ${prefix}/${version}/integrations-tools/external-resources/ -> ${base}/${version}/ecosystem/ +[*-v8.1]: ${prefix}/${version}/compatibility/ -> ${base}/${version}/reference/compatibility/ +[*-v8.1]: ${prefix}/${version}/configuration/ -> ${base}/${version}/reference/configuration/ +[*-v8.1]: ${prefix}/${version}/integrations-tools/rails-integration/ -> ${base}/${version}/reference/rails-integration/ +[*-v8.1]: ${prefix}/${version}/quick-start-sinatra/ -> ${base}/${version}/tutorials/getting-started-sinatra/ +[*-v8.1]: ${prefix}/${version}/quick-start-rails/ -> ${base}/${version}/tutorials/getting-started-rails7/ +[*-v8.1]: ${prefix}/${version}/data-modeling/documents/ -> ${base}/${version}/tutorials/documents/ +[*-v8.1]: ${prefix}/${version}/security/encryption/ -> ${base}/${version}/tutorials/automatic-encryption/ +[*-v8.1]: ${prefix}/${version}/data-modeling/field-types/ -> ${base}/${version}/reference/fields/ +[*-v8.1]: ${prefix}/${version}/data-modeling/inheritance/ -> ${base}/${version}/reference/inheritance/ +[*-v8.1]: ${prefix}/${version}/data-modeling/associations/ -> ${base}/${version}/reference/associations/ +[*-v8.1]: ${prefix}/${version}/data-modeling/validation/ -> ${base}/${version}/reference/validation/ +[*-v8.1]: ${prefix}/${version}/configuration/collection-config/ -> ${base}/${version}/reference/collection-configuration/ +[*-v8.1]: ${prefix}/${version}/data-modeling/indexes/ -> ${base}/${version}/reference/indexes/ +[*-v8.1]: ${prefix}/${version}/configuration/sharding/ -> ${base}/${version}/reference/sharding/ +[*-v8.1]: ${prefix}/${version}/interact-data/crud/ -> ${base}/${version}/reference/crud/ +[*-v8.1]: ${prefix}/${version}/interact-data/specify-query/ -> ${base}/${version}/reference/queries/ +[*-v8.1]: ${prefix}/${version}/interact-data/text-search/ -> ${base}/${version}/reference/text-search/ +[*-v8.1]: ${prefix}/${version}/interact-data/aggregation/ -> ${base}/${version}/reference/aggregation/ +[*-v8.1]: ${prefix}/${version}/configuration/persistence-config/ -> ${base}/${version}/reference/persistence-configuration/ +[*-v8.1]: ${prefix}/${version}/interact-data/nested-attributes/ -> ${base}/${version}/reference/nested-attributes/ +[*-v8.1]: ${prefix}/${version}/data-modeling/callbacks/ -> ${base}/${version}/reference/callbacks/ +[*-v8.1]: ${prefix}/${version}/interact-data/transaction/ -> ${base}/${version}/reference/transactions/ +[*-v8.1]: ${prefix}/${version}/whats-new/ -> ${base}/${version}/release-notes/mongoid-9.0/ +[*-v8.1]: ${prefix}/${version}/code-documentation/ -> ${base}/${version}/contributing/code-documentation/ +[*-v8.1]: ${prefix}/${version}/issues-and-help/ -> ${base}/${version}/contributing/contributing-guidelines/ +[*-v8.1]: ${prefix}/${version}/integrations-tools/external-resources/ -> ${base}/${version}/ecosystem/ # Redirects for old page URLs in standardized (new) docs -[master]: ${prefix}/${version}/installation/ -> ${base}/${version}/#quick-start -[master]: ${prefix}/${version}/reference/compatibility/ -> ${base}/${version}/compatibility/ -[master]: ${prefix}/${version}/reference/configuration/ -> ${base}/${version}/configuration/ -[master]: ${prefix}/${version}/reference/rails-integration/ -> ${base}/${version}/integrations-tools/rails-integration/ -[master]: ${prefix}/${version}/tutorials/getting-started-sinatra/ -> ${base}/${version}/quick-start-sinatra/ -[master]: ${prefix}/${version}/tutorials/getting-started-rails7/ -> ${base}/${version}/quick-start-rails/ -[master]: ${prefix}/${version}/tutorials/getting-started-rails6/ -> ${base}/${version}/quick-start-rails/ -[master]: ${prefix}/${version}/tutorials/documents/ -> ${base}/${version}/data-modeling/documents/ -[master]: ${prefix}/${version}/tutorials/common-errors/ -> ${base}/${version}/ -[master]: ${prefix}/${version}/tutorials/automatic-encryption/ -> ${base}/${version}/security/encryption/ -[master]: ${prefix}/${version}/reference/fields/ -> ${base}/${version}/data-modeling/field-types/ -[master]: ${prefix}/${version}/reference/inheritance/ -> ${base}/${version}/data-modeling/inheritance/ -[master]: ${prefix}/${version}/reference/associations/ -> ${base}/${version}/data-modeling/associations/ -[master]: ${prefix}/${version}/reference/validation/ -> ${base}/${version}/data-modeling/validation/ -[master]: ${prefix}/${version}/reference/collection-configuration/ -> ${base}/${version}/configuration/collection-config/ -[master]: ${prefix}/${version}/reference/indexes/ -> ${base}/${version}/data-modeling/indexes/ -[master]: ${prefix}/${version}/reference/sharding/ -> ${base}/${version}/configuration/sharding/ -[master]: ${prefix}/${version}/reference/crud/ -> ${base}/${version}/interact-data/crud/ -[master]: ${prefix}/${version}/reference/queries/ -> ${base}/${version}/interact-data/specify-query/ -[master]: ${prefix}/${version}/reference/text-search/ -> ${base}/${version}/interact-data/text-search/ -[master]: ${prefix}/${version}/reference/aggregation/ -> ${base}/${version}/interact-data/aggregation/ -[master]: ${prefix}/${version}/reference/map-reduce/ -> ${base}/${version}/interact-data/aggregation/ -[master]: ${prefix}/${version}/reference/persistence-configuration/ -> ${base}/${version}/configuration/persistence-config/ -[master]: ${prefix}/${version}/reference/nested-attributes/ -> ${base}/${version}/interact-data/nested-attributes/ -[master]: ${prefix}/${version}/reference/callbacks/ -> ${base}/${version}/data-modeling/callbacks/ -[master]: ${prefix}/${version}/reference/sessions/ -> ${base}/${version}/interact-data/transaction/ -[master]: ${prefix}/${version}/reference/transactions/ -> ${base}/${version}/interact-data/transaction/ -[master]: ${prefix}/${version}/release-notes/upgrading/ -> ${base}/${version}/whats-new/ -[master]: ${prefix}/${version}/release-notes/mongoid-9.0/ -> ${base}/${version}/whats-new/ -[master]: ${prefix}/${version}/contributing/code-documentation/ -> ${base}/${version}/code-documentation/ -[master]: ${prefix}/${version}/contributing/contributing-guidelines/ -> ${base}/${version}/issues-and-help/ -[master]: ${prefix}/${version}/additional-resources/ -> ${base}/${version}/integrations-tools/external-resources/ -[master]: ${prefix}/${version}/ecosystem/ -> ${base}/${version}/integrations-tools/external-resources/ +[v9.0-*]: ${prefix}/${version}/installation/ -> ${base}/${version}/#quick-start +[v9.0-*]: ${prefix}/${version}/reference/compatibility/ -> ${base}/${version}/compatibility/ +[v9.0-*]: ${prefix}/${version}/reference/configuration/ -> ${base}/${version}/configuration/ +[v9.0-*]: ${prefix}/${version}/reference/rails-integration/ -> ${base}/${version}/integrations-tools/rails-integration/ +[v9.0-*]: ${prefix}/${version}/tutorials/getting-started-sinatra/ -> ${base}/${version}/quick-start-sinatra/ +[v9.0-*]: ${prefix}/${version}/tutorials/getting-started-rails7/ -> ${base}/${version}/quick-start-rails/ +[v9.0-*]: ${prefix}/${version}/tutorials/getting-started-rails6/ -> ${base}/${version}/quick-start-rails/ +[v9.0-*]: ${prefix}/${version}/tutorials/documents/ -> ${base}/${version}/data-modeling/documents/ +[v9.0-*]: ${prefix}/${version}/tutorials/common-errors/ -> ${base}/${version}/ +[v9.0-*]: ${prefix}/${version}/tutorials/automatic-encryption/ -> ${base}/${version}/security/encryption/ +[v9.0-*]: ${prefix}/${version}/reference/fields/ -> ${base}/${version}/data-modeling/field-types/ +[v9.0-*]: ${prefix}/${version}/reference/inheritance/ -> ${base}/${version}/data-modeling/inheritance/ +[v9.0-*]: ${prefix}/${version}/reference/associations/ -> ${base}/${version}/data-modeling/associations/ +[v9.0-*]: ${prefix}/${version}/reference/validation/ -> ${base}/${version}/data-modeling/validation/ +[v9.0-*]: ${prefix}/${version}/reference/collection-configuration/ -> ${base}/${version}/configuration/collection-config/ +[v9.0-*]: ${prefix}/${version}/reference/indexes/ -> ${base}/${version}/data-modeling/indexes/ +[v9.0-*]: ${prefix}/${version}/reference/sharding/ -> ${base}/${version}/configuration/sharding/ +[v9.0-*]: ${prefix}/${version}/reference/crud/ -> ${base}/${version}/interact-data/crud/ +[v9.0-*]: ${prefix}/${version}/reference/queries/ -> ${base}/${version}/interact-data/specify-query/ +[v9.0-*]: ${prefix}/${version}/reference/text-search/ -> ${base}/${version}/interact-data/text-search/ +[v9.0-*]: ${prefix}/${version}/reference/aggregation/ -> ${base}/${version}/interact-data/aggregation/ +[v9.0-*]: ${prefix}/${version}/reference/map-reduce/ -> ${base}/${version}/interact-data/aggregation/ +[v9.0-*]: ${prefix}/${version}/reference/persistence-configuration/ -> ${base}/${version}/configuration/persistence-config/ +[v9.0-*]: ${prefix}/${version}/reference/nested-attributes/ -> ${base}/${version}/interact-data/nested-attributes/ +[v9.0-*]: ${prefix}/${version}/reference/callbacks/ -> ${base}/${version}/data-modeling/callbacks/ +[v9.0-*]: ${prefix}/${version}/reference/sessions/ -> ${base}/${version}/interact-data/transaction/ +[v9.0-*]: ${prefix}/${version}/reference/transactions/ -> ${base}/${version}/interact-data/transaction/ +[v9.0-*]: ${prefix}/${version}/release-notes/upgrading/ -> ${base}/${version}/whats-new/ +[v9.0-*]: ${prefix}/${version}/release-notes/mongoid-9.0/ -> ${base}/${version}/whats-new/ +[v9.0-*]: ${prefix}/${version}/contributing/code-documentation/ -> ${base}/${version}/code-documentation/ +[v9.0-*]: ${prefix}/${version}/contributing/contributing-guidelines/ -> ${base}/${version}/issues-and-help/ +[v9.0-*]: ${prefix}/${version}/additional-resources/ -> ${base}/${version}/integrations-tools/external-resources/ +[v9.0-*]: ${prefix}/${version}/ecosystem/ -> ${base}/${version}/integrations-tools/external-resources/