Skip to content

Commit

Permalink
- Prefer defining field type value as a Symbol rather than a Class. (…
Browse files Browse the repository at this point in the history
…Symbol is already supported today.)

- Deprecate using field type as a Class.
- Add ability to define custom field types using a mini DSL (Mongoid::Fields.configure)
- Fix Mongoid::Fields.option documentation
  • Loading branch information
johnnyshields committed Apr 5, 2022
1 parent eb1c9bb commit 22222c6
Show file tree
Hide file tree
Showing 10 changed files with 535 additions and 148 deletions.
125 changes: 72 additions & 53 deletions docs/reference/fields.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,34 +55,40 @@ on a person by using the ``field`` macro.

class Person
include Mongoid::Document
field :name, type: String
field :date_of_birth, type: Date
field :weight, type: Float
field :name, type: :string
field :date_of_birth, type: :date
field :weight, type: :float
end

Below is a list of valid types for fields.

- ``Array``
- ``BigDecimal``
- ``Boolean``
- ``Date``
- ``DateTime``
- ``Float``
- ``Hash``
- ``Integer``
- ``BSON::ObjectId``
- ``BSON::Binary``
- ``Range``
- ``Regexp``
- ``Set``
- ``String``
- ``StringifiedSymbol``
- ``Symbol``
- ``Time``
- ``TimeWithZone``
- ``:array``
- ``:big_decimal``
- ``:boolean``
- ``:date``
- ``:date_time``
- ``:decimal128`` (uses ``BSON::Decimal128``)
- ``:float``
- ``:hash``
- ``:integer``
- ``:object_id`` (uses ``BSON::ObjectID``)
- ``:binary`` (uses ``BSON::Binary``)
- ``:range``
- ``:regexp``
- ``:set``
- ``:string``
- ``:stringified_symbol`` (see below)
- ``:symbol``
- ``:time``
- ``:time_with_zone``

To define custom field types, refer to :ref:`Custom Field Types <custom-field-types>` below.

As of Mongoid 8.0, ``field :type`` should be specified as a ``Symbol``.
Specifying as a ``Class`` is deprecated and will be no longer supported in a
future major version of Mongoid. Unrecognized field type symbols will result
in an `InvalidFieldType` error when the model class is loaded.


.. _omitting-field-type-definition:

Expand Down Expand Up @@ -116,16 +122,16 @@ Types that are not supported as dynamic attributes since they cannot be cast are

.. _field-type-stringified-symbol:

Field Type: StringifiedSymbol
-----------------------------
Field Type :stringified_symbol
------------------------------

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,
The ``:stringified_symbol`` 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 <field-type-symbol>`.
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
without native symbol types, so the ``:stringified_symbol`` type allows the use of symbols
while ensuring interoperability with other drivers. The ``:stringified_symbol`` type stores all data
on the database as strings, while exposing values to the application as symbols.

An example usage is shown below:
Expand Down Expand Up @@ -170,15 +176,15 @@ migration from fields that currently store either strings or BSON symbols in the

.. _field-type-symbol:

Field Type: Symbol
Field Type :symbol
------------------

New applications should use the :ref:`StringifiedSymbol field type <field-type-stringified-symbol>`
to store Ruby symbols in the database. The ``StringifiedSymbol`` field type
New applications should use the :ref:`:stringified_symbol field type <field-type-stringified-symbol>`
to store Ruby symbols in the database. The ``:stringified_symbol`` 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
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
Expand All @@ -201,10 +207,10 @@ snippet in your project:

.. _field-type-hash:

Field Type: Hash
Field Type :hash
----------------

When using a field of type Hash, be wary of adhering to the
When using a field of type ``:hash``, be wary of adhering to the
`legal key names for mongoDB <http://docs.mongodb.org/manual/reference/limits/#naming-restrictions>`_,
or else the values will not store properly.

Expand All @@ -213,7 +219,7 @@ or else the values will not store properly.
class Person
include Mongoid::Document
field :first_name
field :url, type: Hash
field :url, type: :hash

# will update the fields properly and save the values
def set_vals
Expand All @@ -233,21 +239,21 @@ or else the values will not store properly.

.. _field-type-time:

Field Type: Time
Field Type :time
----------------

``Time`` fields store values as ``Time`` instances in the :ref:`configured
``:time`` fields store values as ``Time`` instances in the :ref:`configured
time zone <time-zones>`.

``Date`` and ``DateTime`` instances are converted to ``Time`` instances upon
assignment to a ``Time`` field:
assignment to a ``:time`` field:

.. code-block:: ruby

class Voter
include Mongoid::Document

field :registered_at, type: Time
field :registered_at, type: :time
end

Voter.new(registered_at: Date.today)
Expand All @@ -259,10 +265,10 @@ local time, because the application was not configured to use UTC times.

.. _field-type-date:

Field Type: Date
Field Type :date
----------------

Mongoid allows assignment of values of several types to ``Date`` fields:
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
Expand All @@ -280,20 +286,20 @@ 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``.
``:date``.


.. _field-type-date-time:

Field Type: DateTime
Field Type :date_time
---------------------

MongoDB stores all times as UTC timestamps. When assigning a value to a
``DateTime`` field, or when querying a ``DateTime`` field, Mongoid
``:date_time`` field, or when querying a ``:date_time`` 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``, ``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 <time-zones>`.
Expand All @@ -302,7 +308,7 @@ returned is defined by the :ref:`configured time zone settings <time-zones>`.

class Ticket
include Mongoid::Document
field :opened_at, type: DateTime
field :opened_at, type: :date_time
end

Mongoid.use_activesupport_time_zone = true
Expand Down Expand Up @@ -332,7 +338,7 @@ doing so, the integers/floats are assumed to be Unix timestamps (in UTC):
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
If a ``String`` is used as a ``:date_time`` 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 <time-zones>` is used:

Expand All @@ -354,7 +360,7 @@ If a time zone is specified, it is respected:

.. _field-type-regexp:

Field Type: Regexp
Field Type :regexp
------------------

MongoDB supports storing regular expressions in documents, and querying using
Expand All @@ -365,7 +371,7 @@ fork of `Oniguruma regular expression engine <https://github.com/kkos/oniguruma>
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
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:
Expand All @@ -375,7 +381,7 @@ instance:
class Token
include Mongoid::Document

field :pattern, type: Regexp
field :pattern, type: :regexp
end

token = Token.create!(pattern: /hello.world/m)
Expand Down Expand Up @@ -847,9 +853,20 @@ can use in our model class as follows:

class Profile
include Mongoid::Document
field :location, type: Point
field :location, type: :point
end

First, declare the new field type mapping in an initializer:

.. code-block:: ruby

# in /config/initializers/mongoid_custom_fields.rb

Mongoid::Fields.configure do
type :point, Point
end


Then make a Ruby class to represent the type. This class must define methods
used for MongoDB serialization and deserialization as follows:

Expand Down Expand Up @@ -945,8 +962,10 @@ specifiying its handler function as a block:

# in /config/initializers/mongoid_custom_fields.rb

Mongoid::Fields.option :required do |model, field, value|
model.validates_presence_of field if value
Mongoid::Fields.configure do
option :required do |model, field, value|
model.validates_presence_of field.name if value
end
end

Then, use it your model class:
Expand Down
37 changes: 37 additions & 0 deletions docs/release-notes/mongoid-8.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,40 @@ as well as ActiveRecord-compatible ``previously_new_record?`` and

user.destroy
user.previously_persisted? # => true


Setting Field Type as a Class is Deprecated
-------------------------------------------

Mongoid has historically supported defining the ``field :type`` option
as either a Symbol or a Class. As of Mongoid 8.0, using a Class is deprecated
and Symbol is preferred. Support for ``field :type`` as a Class will be
removed in a future major version of Mongoid.

.. code-block:: ruby

class Person
include Mongoid::Document

# Deprecated; will log an warning message.
field :first_name, type: String

# Good
field :last_name, type: :string
end


Support for Defining Custom Field Type Values
---------------------------------------------

Mongoid 8.0 adds the ability to define custom ``field :type`` Symbol values as follows:

.. code-block:: ruby

# in /config/initializers/mongoid_custom_fields.rb

Mongoid::Fields.configure do
type :point, Point
end

Refer to the :ref:`docs <http://docs.mongodb.org/manual/reference/fields/#custom-field-types>` for details.
22 changes: 20 additions & 2 deletions lib/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,33 @@ en:
resolution: "When defining the field :%{name} on '%{klass}', please provide
valid options for the field. These are currently: %{valid}. If you
meant to define a custom field option, please do so first as follows:\n\n
\_\_Mongoid::Fields.option :%{option} do |model, field, value|\n
\_\_\_\_# Your logic here...\n
\_\_Mongoid::Fields.configure do\n
\_\_\_\_option :%{option} do |model, field, value|\n
\_\_\_\_\_\_# Your logic here...\n
\_\_\_\_end\n
\_\_end\n
\_\_class %{klass}\n
\_\_\_\_include Mongoid::Document\n
\_\_\_\_field :%{name}, %{option}: true\n
\_\_end\n\n
Refer to:
https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-options"
invalid_field_type:
message: "Invalid field type :%{type} for field :%{field} on model '%{klass}'."
summary: "Model '%{klass}' defines a field :%{field} with an unknown :type value
:%{type}. This value is neither present in Mongoid's default type mapping,
nor defined in a custom field type mapping."
resolution: "Please provide a valid :type value for the field. If you
meant to define a custom field type, please do so first as follows:\n\n
\_\_Mongoid::Fields.configure do\n
\_\_\_\_type :%{type}, YourTypeClass
\_\_end\n
\_\_class %{klass}\n
\_\_\_\_include Mongoid::Document\n
\_\_\_\_field :%{field}, type: :%{type}\n
\_\_end\n\n
Refer to:
https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-types"
invalid_includes:
message: "Invalid includes directive: %{klass}.includes(%{args})"
summary: "Eager loading in Mongoid only supports providing arguments
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require "mongoid/errors/invalid_dependent_strategy"
require "mongoid/errors/invalid_field"
require "mongoid/errors/invalid_field_option"
require "mongoid/errors/invalid_field_type"
require "mongoid/errors/invalid_find"
require "mongoid/errors/invalid_includes"
require "mongoid/errors/invalid_index"
Expand Down
25 changes: 25 additions & 0 deletions lib/mongoid/errors/invalid_field_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Mongoid
module Errors

# This error is raised when trying to define a field using a :type option value
# that is not present in the field type mapping.
class InvalidFieldType < MongoidError

# Create the new error.
#
# @example Instantiate the error.
# InvalidFieldType.new('Person', 'first_name', 'stringgy')
#
# @param [ String ] klass The model class.
# @param [ String ] field The field on which the invalid type is used.
# @param [ String ] type The value of the field :type option.
def initialize(klass, field, type)
super(
compose_message('invalid_field_type', { klass: klass, field: field, type: type })
)
end
end
end
end

0 comments on commit 22222c6

Please sign in to comment.