@@ -109,7 +109,6 @@ You can safely omit type specifications when:
109109
110110Types that are not supported as dynamic attributes since they cannot be cast are:
111111
112- - ``BigDecimal``
113112- ``Date``
114113- ``DateTime``
115114- ``Range``
@@ -247,10 +246,10 @@ assignment to a ``Time`` field:
247246
248247 class Voter
249248 include Mongoid::Document
250-
249+
251250 field :registered_at, type: Time
252251 end
253-
252+
254253 Voter.new(registered_at: Date.today)
255254 # => #<Voter _id: 5fdd80392c97a618f07ba344, registered_at: 2020-12-18 05:00:00 UTC>
256255
@@ -416,6 +415,107 @@ matches strings containing "hello" before a newline, besides strings ending in
416415This is because the meaning of ``$`` is different between PCRE and Ruby
417416regular expressions.
418417
418+ BigDecimal Fields
419+ -----------------
420+
421+ The ``BigDecimal`` field type is used to store numbers with increased precision.
422+
423+ The ``BigDecimal`` field type stores its values in two different ways in the
424+ database, depending on the value of the ``Mongoid.map_big_decimal_to_decimal128``
425+ global config option. If this flag is set to false (which is the default),
426+ the ``BigDecimal`` field will be stored as a string, otherwise it will be stored
427+ as a ``BSON::Decimal128``.
428+
429+ The ``BigDecimal`` field type has some limitations when converting to and from
430+ a ``BSON::Decimal128``:
431+
432+ - ``BSON::Decimal128`` has a limited range and precision, while ``BigDecimal``
433+ has no restrictions in terms of range and precision. ``BSON::Decimal128`` has
434+ a max value of approximately ``10^6145`` and a min value of approximately
435+ ``-10^6145``, and has a maximum of 34 bits of precision. When attempting to
436+ store values that don't fit into a ``BSON::Decimal128``, it is recommended to
437+ have them stored as a string instead of a ``BSON::Decimal128``. You can do
438+ that by setting ``Mongoid.map_big_decimal_to_decimal128`` to ``false``. If a
439+ value that does not fit in a ``BSON::Decimal128`` is attempted to be stored
440+ as one, an error will be raised.
441+
442+ - ``BSON::Decimal128`` is able to accept signed ``NaN`` values, while
443+ ``BigDecimal`` is not. When retrieving signed ``NaN`` values from
444+ the database using the ``BigDecimal`` field type, the ``NaN`` will be
445+ unsigned.
446+
447+ - ``BSON::Decimal128`` maintains trailing zeroes when stored in the database.
448+ ``BigDecimal``, however, does not maintain trailing zeroes, and therefore
449+ retrieving ``BSON::Decimal128`` values using the ``BigDecimal`` field type
450+ may result in a loss of precision.
451+
452+ There is an additional caveat when storing a ``BigDecimal`` in a field with no
453+ type (i.e. a dynamically typed field) and ``Mongoid.map_big_decimal_to_decimal128``
454+ is ``false``. In this case, the ``BigDecimal`` is stored as a string, and since a
455+ dynamic field is being used, querying for that field with a ``BigDecimal`` will
456+ not find the string for that ``BigDecimal``, since the query is looking for a
457+ ``BigDecimal``. In order to query for that string, the ``BigDecimal`` must
458+ first be converted to a string with ``to_s``. Note that this is not a problem
459+ when the field has type ``BigDecimal``.
460+
461+ If you wish to avoid using ``BigDecimal`` altogether, you can set the field
462+ type to ``BSON::Decimal128``. This will allow you to keep track of trailing
463+ zeroes and signed ``NaN`` values.
464+
465+ Migration to ``decimal128``-backed ``BigDecimal`` Field
466+ ```````````````````````````````````````````````````````
467+ In a future major version of Mongoid, the ``Mongoid.map_big_decimal_to_decimal128``
468+ global config option will be defaulted to ``true``. When this flag is turned on,
469+ ``BigDecimal`` values in queries will not match to the strings that are already
470+ stored in the database; they will only match to ``decimal128`` values that are
471+ in the database. If you have a ``BigDecimal`` field that is backed by strings,
472+ you have three options:
473+
474+ 1. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be
475+ set to ``false``, and you can continue storing your ``BigDecimal`` values as
476+ strings. Note that you are surrendering the advantages of storing ``BigDecimal``
477+ values as a ``decimal128``, like being able to do queries and aggregations
478+ based on the numerical value of the field.
479+
480+ 2. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be
481+ set to ``true``, and you can convert all values for that field from strings to
482+ ``decimal128`` values in the database. You should do this conversion before
483+ setting the global config option to true. An example query to accomplish this
484+ is as follows:
485+
486+ .. code-block:: javascript
487+
488+ db.bands.updateMany({
489+ "field": { "$exists": true }
490+ }, [
491+ {
492+ "$set": {
493+ "field": { "$toDecimal": "$field" }
494+ }
495+ }
496+ ])
497+
498+ This query updates all documents that have the given field, setting that
499+ field to its corresponding ``decimal128`` value. Note that this query only
500+ works in MongoDB 4.2+.
501+
502+ 3. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be
503+ set to ``true``, and you can have both strings and ``decimal128`` values for
504+ that field. This way, only ``decimal128`` values will be inserted into and
505+ updated to the database going forward. Note that you still don't get the
506+ full advantages of using only ``decimal128`` values, but your dataset is
507+ slowly migrating to all ``decimal128`` values, as old string values are
508+ updated to ``decimal128`` and new ``decimal128`` values are added. With this
509+ setup, you can still query for ``BigDecimal`` values as follows:
510+
511+ .. code-block:: ruby
512+
513+ Mongoid.map_big_decimal_to_decimal128 = true
514+ big_decimal = BigDecimal('2E9')
515+ Band.in(sales: [big_decimal, big_decimal.to_s]).to_a
516+
517+ This query will find all values that are either a ``decimal128`` value or
518+ a string that match that value.
419519
420520.. _field-default-values:
421521
@@ -524,17 +624,17 @@ from the aliased field:
524624
525625 class Band
526626 include Mongoid::Document
527-
627+
528628 field :name, type: String
529629 alias_attribute :n, :name
530630 end
531-
631+
532632 band = Band.new(n: 'Astral Projection')
533633 # => #<Band _id: 5fc1c1ee2c97a64accbeb5e1, name: "Astral Projection">
534-
634+
535635 band.attributes
536636 # => {"_id"=>BSON::ObjectId('5fc1c1ee2c97a64accbeb5e1'), "name"=>"Astral Projection"}
537-
637+
538638 band.n
539639 # => "Astral Projection"
540640
@@ -559,11 +659,11 @@ This is useful for storing different values in ``id`` and ``_id`` fields:
559659
560660 class Band
561661 include Mongoid::Document
562-
662+
563663 unalias_attribute :id
564664 field :id, type: String
565665 end
566-
666+
567667 Band.new(id: '42')
568668 # => #<Band _id: 5fc1c3f42c97a6590684046c, id: "42">
569669
@@ -691,19 +791,19 @@ getter as follows:
691791
692792 class DistanceMeasurement
693793 include Mongoid::Document
694-
794+
695795 field :value, type: Float
696796 field :unit, type: String
697-
797+
698798 def unit
699799 read_attribute(:unit) || "m"
700800 end
701-
801+
702802 def to_s
703803 "#{value} #{unit}"
704804 end
705805 end
706-
806+
707807 measurement = DistanceMeasurement.new(value: 2)
708808 measurement.to_s
709809 # => "2.0 m"
@@ -717,18 +817,18 @@ may be implemented as follows:
717817
718818 class DistanceMeasurement
719819 include Mongoid::Document
720-
820+
721821 field :value, type: Float
722822 field :unit, type: String
723-
823+
724824 def unit=(value)
725825 if value.blank?
726826 value = nil
727827 end
728828 write_attribute(:unit, value)
729829 end
730830 end
731-
831+
732832 measurement = DistanceMeasurement.new(value: 2, unit: "")
733833 measurement.attributes
734834 # => {"_id"=>BSON::ObjectId('613fa15aa15d5d617216104c'), "value"=>2.0, "unit"=>nil}
0 commit comments