Skip to content

Commit 7f6fd08

Browse files
MONGOID-5370 Add collection options support (#5452)
Co-authored-by: Oleg Pudeyev <[email protected]>
1 parent 0e5a20e commit 7f6fd08

File tree

15 files changed

+618
-28
lines changed

15 files changed

+618
-28
lines changed

docs/reference/collection-configuration.txt

Lines changed: 69 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,96 @@ Collection Configuration
1010
:depth: 2
1111
:class: singlecol
1212

13+
Configuring a Document Collection
14+
=================================
1315

14-
Capped Collections
15-
------------------
16+
You can specify collection options for documents using the ``store_in`` macro.
17+
This macro accepts ``:collection_options`` argument, which can contain any collection
18+
options that are supported by the driver.
19+
20+
.. note::
21+
22+
In order to apply the options, the collection must be explicitly created up-front.
23+
This should be done using :ref:`Collection Management Rake Task<collection-management-task>`.
1624

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

21-
To create a capped collection with the driver, first retrieve the client:
29+
.. note::
30+
31+
Collection options depend on the driver version and MongoDB server version.
32+
It is possible that some options, like time series collections, are not available
33+
on older server versions.
34+
35+
Time Series Collection
36+
----------------------
2237

2338
.. code-block:: ruby
2439

25-
class Name
40+
class Measurement
2641
include Mongoid::Document
27-
end
28-
client = Name.collection.client
2942

30-
Then create the collection:
43+
field :temperature, type: Integer
44+
field :timestamp, type: Time
3145

32-
.. code-block:: ruby
46+
store_in collection_options: {
47+
time_series: {
48+
timeField: "timestamp",
49+
granularity: "minutes"
50+
},
51+
expire_after: 604800
52+
}
53+
end
3354

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

36-
To create a capped collection from the Mongo console:
3756

38-
.. code-block:: javascript
57+
Capped Collections
58+
------------------
3959

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

62+
class Name
63+
include Mongoid::Document
64+
65+
store_in collection_options: {
66+
capped: true,
67+
size: 1024
68+
}
69+
end
4270

4371
Set a Default Collation on a Collection
4472
---------------------------------------
4573

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

50-
To create a collection with a default collation with the driver:
76+
class Name
77+
include Mongoid::Document
5178

52-
.. code-block:: ruby
79+
store_in collection_options: {
80+
collation: {
81+
locale: 'fr'
82+
}
83+
}
84+
end
85+
86+
.. _collection-management-task:
87+
88+
Collection Management Rake Task
89+
===============================
5390

54-
client["name", :collation => { :locale => 'fr'}].create
91+
If you specify collection options for a document, then the corresponding collection
92+
must be explicitly created prior to use. To do so, use the provided
93+
``db:mongoid:create_collections`` Rake task:
5594

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

58-
.. code-block:: javascript
97+
$ rake db:mongoid:create_collections
98+
99+
The create collections command also works for just one model by running
100+
in Rails console:
101+
102+
.. code-block:: ruby
59103

60-
db.createCollection("name", { collation: { locale: 'fr' } });
104+
# Create collection for Model
105+
Model.create_collection

lib/config/locales/en.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ en:
4242
since the document did not actually get saved."
4343
resolution: "Double check all before callbacks to make sure they are
4444
not unintentionally returning false."
45+
create_collection_failure:
46+
message: "Cannot create collection %{collection_name}
47+
with options %{collection_options}. The following error was raised:
48+
%{error}."
49+
summary: "The server rejected createCollection command with given collection
50+
options. This may happen when some of the options are invalid, or not
51+
supported by your version of the server."
52+
resolution: "Double check that collection options for the collection
53+
%{collection_name} are valid. Consult with Ruby driver documentation
54+
and MongoDB documentation if the desired options are supported by
55+
your version of the server."
4556
criteria_argument_required:
4657
message: "Calling Criteria methods with nil arguments is not allowed."
4758
summary: "Arguments to Criteria methods cannot be nil, and most
@@ -97,6 +108,12 @@ en:
97108
resolution: "Search for an id/shard key that is in the database or set
98109
the Mongoid.raise_not_found_error configuration option to false,
99110
which will cause nil to be returned instead of raising this error."
111+
drop_collection_failure:
112+
message: "Cannot drop collection %{collection_name}."
113+
summary: "Mongoid tried to drop collection %{collection_name}, but the
114+
collection still exists in the database."
115+
resolution: "Try to drop the collection manually using Ruby driver or
116+
mongo shell."
100117
empty_config_file:
101118
message: "Empty configuration file: %{path}."
102119
summary: "Your mongoid.yml configuration file appears to be empty."
@@ -326,7 +343,7 @@ en:
326343
invalid_storage_options:
327344
message: "Invalid options passed to %{klass}.store_in: %{options}."
328345
summary: "The :store_in macro takes only a hash of parameters with
329-
the keys :database, :collection, or :client."
346+
the keys :database, :collection, :collection_options, or :client."
330347
resolution: "Change the options passed to store_in to match the
331348
documented API, and ensure all keys in the options hash are
332349
symbols.\n\n

lib/mongoid/clients/validators/storage.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module Storage
99
extend self
1010

1111
# The valid options for storage.
12-
VALID_OPTIONS = [ :collection, :database, :client ].freeze
12+
VALID_OPTIONS = [ :collection, :collection_options, :database, :client ].freeze
1313

1414
# Validate the options provided to :store_in.
1515
#
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
5+
# Encapsulates behavior around defining collections.
6+
module CollectionConfigurable
7+
extend ActiveSupport::Concern
8+
9+
module ClassMethods
10+
# Create the collection for the called upon Mongoid model.
11+
#
12+
# This method does not re-create existing collections.
13+
#
14+
# If the document includes `store_in` macro with `collection_options` key,
15+
# these options are used when creating the collection.
16+
#
17+
# @param [ true | false ] force If true, the method will drop existing
18+
# collections before creating new ones. If false, the method will create
19+
# only new collection (that do not exist in the database).
20+
#
21+
# @raise [ Errors::CreateCollectionFailure ] If collection creation failed.
22+
# @raise [ Errors::DropCollectionFailure ] If an attempt to drop collection failed.
23+
def create_collection(force: false)
24+
if collection_name.empty?
25+
# This is most probably an anonymous class, we ignore them.
26+
return
27+
end
28+
if collection_name.match(/^system\./)
29+
# We do not do anything with system collections.
30+
return
31+
end
32+
if force
33+
collection.drop
34+
end
35+
if coll_options = collection.database.list_collections(filter: { name: collection_name.to_s }).first
36+
if force
37+
raise Errors::DropCollectionFailure.new(collection_name)
38+
else
39+
logger.debug(
40+
"MONGOID: Collection '#{collection_name}' already exists " +
41+
"in database '#{database_name}' with options '#{coll_options}'."
42+
)
43+
end
44+
else
45+
begin
46+
collection.database[collection_name, storage_options.fetch(:collection_options, {})].create
47+
rescue Mongo::Error::OperationFailure => e
48+
raise Errors::CreateCollectionFailure.new(
49+
collection_name,
50+
storage_options[:collection_options],
51+
e
52+
)
53+
end
54+
end
55+
end
56+
end
57+
end
58+
end

lib/mongoid/composable.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require "mongoid/changeable"
4+
require "mongoid/collection_configurable"
45
require "mongoid/findable"
56
require "mongoid/indexable"
67
require "mongoid/inspectable"
@@ -36,6 +37,7 @@ module Composable
3637
include Atomic
3738
include Changeable
3839
include Clients
40+
include CollectionConfigurable
3941
include Attributes
4042
include Evolvable
4143
include Fields

lib/mongoid/config.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,17 @@ def register_model(klass)
215215
end
216216
end
217217

218+
# Deregister a model in the application with Mongoid.
219+
#
220+
# @param [ Class ] klass The model to deregister.
221+
#
222+
# @api private
223+
def deregister_model(klass)
224+
LOCK.synchronize do
225+
models.delete(klass)
226+
end
227+
end
228+
218229
# From a hash of settings, load all the configuration.
219230
#
220231
# @example Load the configuration.

lib/mongoid/errors.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require "mongoid/errors/ambiguous_relationship"
55
require "mongoid/errors/attribute_not_loaded"
66
require "mongoid/errors/callback"
7+
require "mongoid/errors/create_collection_failure"
78
require "mongoid/errors/criteria_argument_required"
89
require "mongoid/errors/document_not_destroyed"
910
require "mongoid/errors/document_not_found"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
module Errors
5+
6+
# Raised when an attempt to create a collection failed.
7+
class CreateCollectionFailure < MongoidError
8+
9+
# Instantiate the create collection error.
10+
#
11+
# @param [ String ] collection_name The name of the collection that
12+
# Mongoid failed to create.
13+
# @param [ Hash ] collection_options The options that were used when
14+
# tried to create the collection.
15+
# @param [ Mongo::Error::OperationFailure ] error The error raised when
16+
# tried to create the collection.
17+
#
18+
# @api private
19+
def initialize(collection_name, collection_options, error)
20+
super(
21+
compose_message(
22+
"create_collection_failure",
23+
{
24+
collection_name: collection_name,
25+
collection_options: collection_options,
26+
error: "#{error.class}: #{error.message}"
27+
}
28+
)
29+
)
30+
end
31+
end
32+
end
33+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
module Errors
5+
6+
# Raised when an attempt to drop a collection failed.
7+
class DropCollectionFailure < MongoidError
8+
9+
# Instantiate the drop collection error.
10+
#
11+
# @param [ String ] collection_name The name of the collection that
12+
# Mongoid failed to drop.
13+
#
14+
# @api private
15+
def initialize(collection_name, collection_options, error)
16+
super(
17+
compose_message(
18+
"drop_collection_failure",
19+
{
20+
collection_name: collection_name
21+
}
22+
)
23+
)
24+
end
25+
end
26+
end
27+
end

lib/mongoid/railties/database.rake

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace :db do
2525

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

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

5656
unless Rake::Task.task_defined?("db:test:prepare")
5757
namespace :test do
58-
task :prepare => "mongoid:create_indexes"
58+
task :prepare => ["mongoid:create_collections", "mongoid:create_indexes"]
5959
end
6060
end
6161

62+
unless Rake::Task.task_defined?("db:create_collections")
63+
desc "Create collections specified in Mongoid models"
64+
task :create_collections => "mongoid:create_collections"
65+
end
66+
6267
unless Rake::Task.task_defined?("db:create_indexes")
6368
desc "Create indexes specified in Mongoid models"
6469
task :create_indexes => "mongoid:create_indexes"

0 commit comments

Comments
 (0)