Skip to content

Commit

Permalink
MONGOID-5370 Add collection options support (#5452)
Browse files Browse the repository at this point in the history
Co-authored-by: Oleg Pudeyev <[email protected]>
  • Loading branch information
comandeo-mongo and p-mongo authored Sep 5, 2022
1 parent 0e5a20e commit 7f6fd08
Show file tree
Hide file tree
Showing 15 changed files with 618 additions and 28 deletions.
93 changes: 69 additions & 24 deletions docs/reference/collection-configuration.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,96 @@ Collection Configuration
:depth: 2
:class: singlecol

Configuring a Document Collection
=================================

Capped Collections
------------------
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<collection-management-task>`.

Mongoid does not provide a mechanism for creating capped collections on the fly - you
will need to create these yourself one time up front either with the driver or via the
Mongo console.
Please refer to `the driver collections page
<https://mongodb.com/docs/ruby-driver/current/reference/collection-tasks/>`_
for the more information about collection options.

To create a capped collection with the driver, first retrieve the client:
.. 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 Name
class Measurement
include Mongoid::Document
end
client = Name.collection.client

Then create the collection:
field :temperature, type: Integer
field :timestamp, type: Time

.. code-block:: ruby
store_in collection_options: {
time_series: {
timeField: "timestamp",
granularity: "minutes"
},
expire_after: 604800
}
end

client["names", :capped => true, :size => 1024].create

To create a capped collection from the Mongo console:

.. code-block:: javascript
Capped Collections
------------------

db.createCollection("name", { capped: true, size: 1024 });
.. code-block:: ruby

class Name
include Mongoid::Document

store_in collection_options: {
capped: true,
size: 1024
}
end

Set a Default Collation on a Collection
---------------------------------------

Mongoid does not provide a mechanism for creating a collection with a default collation.
Like capped collections, you will need to create the collection yourself one time, up-front,
either with the driver or via the Mongo console.
.. code-block:: ruby

To create a collection with a default collation with the driver:
class Name
include Mongoid::Document

.. code-block:: ruby
store_in collection_options: {
collation: {
locale: 'fr'
}
}
end

.. _collection-management-task:

Collection Management Rake Task
===============================

client["name", :collation => { :locale => 'fr'}].create
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:

To create a collection with a default collation from the Mongo console:
.. code-block:: bash

.. code-block:: javascript
$ rake db:mongoid:create_collections

The create collections command also works for just one model by running
in Rails console:

.. code-block:: ruby

db.createCollection("name", { collation: { locale: 'fr' } });
# Create collection for Model
Model.create_collection
19 changes: 18 additions & 1 deletion lib/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ en:
since the document did not actually get saved."
resolution: "Double check all before callbacks to make sure they are
not unintentionally returning false."
create_collection_failure:
message: "Cannot create collection %{collection_name}
with options %{collection_options}. The following error was raised:
%{error}."
summary: "The server rejected createCollection command with given collection
options. This may happen when some of the options are invalid, or not
supported by your version of the server."
resolution: "Double check that collection options for the collection
%{collection_name} are valid. Consult with Ruby driver documentation
and MongoDB documentation if the desired options are supported by
your version of the server."
criteria_argument_required:
message: "Calling Criteria methods with nil arguments is not allowed."
summary: "Arguments to Criteria methods cannot be nil, and most
Expand Down Expand Up @@ -97,6 +108,12 @@ en:
resolution: "Search for an id/shard key that is in the database or set
the Mongoid.raise_not_found_error configuration option to false,
which will cause nil to be returned instead of raising this error."
drop_collection_failure:
message: "Cannot drop collection %{collection_name}."
summary: "Mongoid tried to drop collection %{collection_name}, but the
collection still exists in the database."
resolution: "Try to drop the collection manually using Ruby driver or
mongo shell."
empty_config_file:
message: "Empty configuration file: %{path}."
summary: "Your mongoid.yml configuration file appears to be empty."
Expand Down Expand Up @@ -326,7 +343,7 @@ en:
invalid_storage_options:
message: "Invalid options passed to %{klass}.store_in: %{options}."
summary: "The :store_in macro takes only a hash of parameters with
the keys :database, :collection, or :client."
the keys :database, :collection, :collection_options, or :client."
resolution: "Change the options passed to store_in to match the
documented API, and ensure all keys in the options hash are
symbols.\n\n
Expand Down
2 changes: 1 addition & 1 deletion lib/mongoid/clients/validators/storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module Storage
extend self

# The valid options for storage.
VALID_OPTIONS = [ :collection, :database, :client ].freeze
VALID_OPTIONS = [ :collection, :collection_options, :database, :client ].freeze

# Validate the options provided to :store_in.
#
Expand Down
58 changes: 58 additions & 0 deletions lib/mongoid/collection_configurable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

module Mongoid

# Encapsulates behavior around defining collections.
module CollectionConfigurable
extend ActiveSupport::Concern

module ClassMethods
# Create the collection for the called upon Mongoid model.
#
# This method does not re-create existing collections.
#
# If the document includes `store_in` macro with `collection_options` key,
# these options are used when creating the collection.
#
# @param [ true | false ] force If true, the method will drop existing
# collections before creating new ones. If false, the method will create
# only new collection (that do not exist in the database).
#
# @raise [ Errors::CreateCollectionFailure ] If collection creation failed.
# @raise [ Errors::DropCollectionFailure ] If an attempt to drop collection failed.
def create_collection(force: false)
if collection_name.empty?
# This is most probably an anonymous class, we ignore them.
return
end
if collection_name.match(/^system\./)
# We do not do anything with system collections.
return
end
if force
collection.drop
end
if coll_options = collection.database.list_collections(filter: { name: collection_name.to_s }).first
if force
raise Errors::DropCollectionFailure.new(collection_name)
else
logger.debug(
"MONGOID: Collection '#{collection_name}' already exists " +
"in database '#{database_name}' with options '#{coll_options}'."
)
end
else
begin
collection.database[collection_name, storage_options.fetch(:collection_options, {})].create
rescue Mongo::Error::OperationFailure => e
raise Errors::CreateCollectionFailure.new(
collection_name,
storage_options[:collection_options],
e
)
end
end
end
end
end
end
2 changes: 2 additions & 0 deletions lib/mongoid/composable.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "mongoid/changeable"
require "mongoid/collection_configurable"
require "mongoid/findable"
require "mongoid/indexable"
require "mongoid/inspectable"
Expand Down Expand Up @@ -36,6 +37,7 @@ module Composable
include Atomic
include Changeable
include Clients
include CollectionConfigurable
include Attributes
include Evolvable
include Fields
Expand Down
11 changes: 11 additions & 0 deletions lib/mongoid/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ def register_model(klass)
end
end

# Deregister a model in the application with Mongoid.
#
# @param [ Class ] klass The model to deregister.
#
# @api private
def deregister_model(klass)
LOCK.synchronize do
models.delete(klass)
end
end

# From a hash of settings, load all the configuration.
#
# @example Load the configuration.
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require "mongoid/errors/ambiguous_relationship"
require "mongoid/errors/attribute_not_loaded"
require "mongoid/errors/callback"
require "mongoid/errors/create_collection_failure"
require "mongoid/errors/criteria_argument_required"
require "mongoid/errors/document_not_destroyed"
require "mongoid/errors/document_not_found"
Expand Down
33 changes: 33 additions & 0 deletions lib/mongoid/errors/create_collection_failure.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module Mongoid
module Errors

# Raised when an attempt to create a collection failed.
class CreateCollectionFailure < MongoidError

# Instantiate the create collection error.
#
# @param [ String ] collection_name The name of the collection that
# Mongoid failed to create.
# @param [ Hash ] collection_options The options that were used when
# tried to create the collection.
# @param [ Mongo::Error::OperationFailure ] error The error raised when
# tried to create the collection.
#
# @api private
def initialize(collection_name, collection_options, error)
super(
compose_message(
"create_collection_failure",
{
collection_name: collection_name,
collection_options: collection_options,
error: "#{error.class}: #{error.message}"
}
)
)
end
end
end
end
27 changes: 27 additions & 0 deletions lib/mongoid/errors/drop_collection_failure.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Mongoid
module Errors

# Raised when an attempt to drop a collection failed.
class DropCollectionFailure < MongoidError

# Instantiate the drop collection error.
#
# @param [ String ] collection_name The name of the collection that
# Mongoid failed to drop.
#
# @api private
def initialize(collection_name, collection_options, error)
super(
compose_message(
"drop_collection_failure",
{
collection_name: collection_name
}
)
)
end
end
end
end
9 changes: 7 additions & 2 deletions lib/mongoid/railties/database.rake
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace :db do

unless Rake::Task.task_defined?("db:setup")
desc "Create the database, and initialize with the seed data"
task :setup => [ "db:create", "mongoid:create_indexes", "db:seed" ]
task :setup => [ "db:create", "mongoid:create_collections", "mongoid:create_indexes", "db:seed" ]
end

unless Rake::Task.task_defined?("db:reset")
Expand Down Expand Up @@ -55,10 +55,15 @@ namespace :db do

unless Rake::Task.task_defined?("db:test:prepare")
namespace :test do
task :prepare => "mongoid:create_indexes"
task :prepare => ["mongoid:create_collections", "mongoid:create_indexes"]
end
end

unless Rake::Task.task_defined?("db:create_collections")
desc "Create collections specified in Mongoid models"
task :create_collections => "mongoid:create_collections"
end

unless Rake::Task.task_defined?("db:create_indexes")
desc "Create indexes specified in Mongoid models"
task :create_indexes => "mongoid:create_indexes"
Expand Down
12 changes: 12 additions & 0 deletions lib/mongoid/tasks/database.rake
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ namespace :db do
task :load_models do
end

desc "Create collections for Mongoid models"
task :create_collections => [:environment, :load_models] do
::Mongoid::Tasks::Database.create_collections
end

desc "Create indexes specified in Mongoid models"
task :create_indexes => [:environment, :load_models] do
::Mongoid::Tasks::Database.create_indexes
Expand Down Expand Up @@ -34,5 +39,12 @@ namespace :db do
task :purge => :environment do
::Mongoid.purge!
end

namespace :create_collections do
desc "Drop and create collections for Mongoid models"
task :force => [:environment, :load_models] do
::Mongoid::Tasks::Database.create_collections(force: true)
end
end
end
end
Loading

0 comments on commit 7f6fd08

Please sign in to comment.