From 35c33cfa5240020a8c73d45379d0a4901e7a381f Mon Sep 17 00:00:00 2001 From: rustagir Date: Fri, 1 Nov 2024 16:07:19 -0400 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 78f72842cdd8d4fee3c944e8c8e60d50a1d267f0 Mon Sep 17 00:00:00 2001 From: rustagir Date: Mon, 4 Nov 2024 13:54:23 -0500 Subject: [PATCH 6/6] 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