@@ -115,7 +115,6 @@ You can safely omit type specifications when:
115115
116116Types that are not supported as dynamic attributes since they cannot be cast are:
117117
118- - ``BigDecimal``
119118- ``Date``
120119- ``DateTime``
121120- ``Range``
@@ -256,7 +255,7 @@ assignment to a ``:time`` field:
256255
257256 field :registered_at, type: :time
258257 end
259-
258+
260259 Voter.new(registered_at: Date.today)
261260 # => #<Voter _id: 5fdd80392c97a618f07ba344, registered_at: 2020-12-18 05:00:00 UTC>
262261
@@ -422,6 +421,107 @@ matches strings containing "hello" before a newline, besides strings ending in
422421This is because the meaning of ``$`` is different between PCRE and Ruby
423422regular expressions.
424423
424+ BigDecimal Fields
425+ -----------------
426+
427+ The ``BigDecimal`` field type is used to store numbers with increased precision.
428+
429+ The ``BigDecimal`` field type stores its values in two different ways in the
430+ database, depending on the value of the ``Mongoid.map_big_decimal_to_decimal128``
431+ global config option. If this flag is set to false (which is the default),
432+ the ``BigDecimal`` field will be stored as a string, otherwise it will be stored
433+ as a ``BSON::Decimal128``.
434+
435+ The ``BigDecimal`` field type has some limitations when converting to and from
436+ a ``BSON::Decimal128``:
437+
438+ - ``BSON::Decimal128`` has a limited range and precision, while ``BigDecimal``
439+ has no restrictions in terms of range and precision. ``BSON::Decimal128`` has
440+ a max value of approximately ``10^6145`` and a min value of approximately
441+ ``-10^6145``, and has a maximum of 34 bits of precision. When attempting to
442+ store values that don't fit into a ``BSON::Decimal128``, it is recommended to
443+ have them stored as a string instead of a ``BSON::Decimal128``. You can do
444+ that by setting ``Mongoid.map_big_decimal_to_decimal128`` to ``false``. If a
445+ value that does not fit in a ``BSON::Decimal128`` is attempted to be stored
446+ as one, an error will be raised.
447+
448+ - ``BSON::Decimal128`` is able to accept signed ``NaN`` values, while
449+ ``BigDecimal`` is not. When retrieving signed ``NaN`` values from
450+ the database using the ``BigDecimal`` field type, the ``NaN`` will be
451+ unsigned.
452+
453+ - ``BSON::Decimal128`` maintains trailing zeroes when stored in the database.
454+ ``BigDecimal``, however, does not maintain trailing zeroes, and therefore
455+ retrieving ``BSON::Decimal128`` values using the ``BigDecimal`` field type
456+ may result in a loss of precision.
457+
458+ There is an additional caveat when storing a ``BigDecimal`` in a field with no
459+ type (i.e. a dynamically typed field) and ``Mongoid.map_big_decimal_to_decimal128``
460+ is ``false``. In this case, the ``BigDecimal`` is stored as a string, and since a
461+ dynamic field is being used, querying for that field with a ``BigDecimal`` will
462+ not find the string for that ``BigDecimal``, since the query is looking for a
463+ ``BigDecimal``. In order to query for that string, the ``BigDecimal`` must
464+ first be converted to a string with ``to_s``. Note that this is not a problem
465+ when the field has type ``BigDecimal``.
466+
467+ If you wish to avoid using ``BigDecimal`` altogether, you can set the field
468+ type to ``BSON::Decimal128``. This will allow you to keep track of trailing
469+ zeroes and signed ``NaN`` values.
470+
471+ Migration to ``decimal128``-backed ``BigDecimal`` Field
472+ ```````````````````````````````````````````````````````
473+ In a future major version of Mongoid, the ``Mongoid.map_big_decimal_to_decimal128``
474+ global config option will be defaulted to ``true``. When this flag is turned on,
475+ ``BigDecimal`` values in queries will not match to the strings that are already
476+ stored in the database; they will only match to ``decimal128`` values that are
477+ in the database. If you have a ``BigDecimal`` field that is backed by strings,
478+ you have three options:
479+
480+ 1. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be
481+ set to ``false``, and you can continue storing your ``BigDecimal`` values as
482+ strings. Note that you are surrendering the advantages of storing ``BigDecimal``
483+ values as a ``decimal128``, like being able to do queries and aggregations
484+ based on the numerical value of the field.
485+
486+ 2. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be
487+ set to ``true``, and you can convert all values for that field from strings to
488+ ``decimal128`` values in the database. You should do this conversion before
489+ setting the global config option to true. An example query to accomplish this
490+ is as follows:
491+
492+ .. code-block:: javascript
493+
494+ db.bands.updateMany({
495+ "field": { "$exists": true }
496+ }, [
497+ {
498+ "$set": {
499+ "field": { "$toDecimal": "$field" }
500+ }
501+ }
502+ ])
503+
504+ This query updates all documents that have the given field, setting that
505+ field to its corresponding ``decimal128`` value. Note that this query only
506+ works in MongoDB 4.2+.
507+
508+ 3. The ``Mongoid.map_big_decimal_to_decimal128`` global config option can be
509+ set to ``true``, and you can have both strings and ``decimal128`` values for
510+ that field. This way, only ``decimal128`` values will be inserted into and
511+ updated to the database going forward. Note that you still don't get the
512+ full advantages of using only ``decimal128`` values, but your dataset is
513+ slowly migrating to all ``decimal128`` values, as old string values are
514+ updated to ``decimal128`` and new ``decimal128`` values are added. With this
515+ setup, you can still query for ``BigDecimal`` values as follows:
516+
517+ .. code-block:: ruby
518+
519+ Mongoid.map_big_decimal_to_decimal128 = true
520+ big_decimal = BigDecimal('2E9')
521+ Band.in(sales: [big_decimal, big_decimal.to_s]).to_a
522+
523+ This query will find all values that are either a ``decimal128`` value or
524+ a string that match that value.
425525
426526.. _field-default-values:
427527
@@ -530,17 +630,17 @@ from the aliased field:
530630
531631 class Band
532632 include Mongoid::Document
533-
633+
534634 field :name, type: String
535635 alias_attribute :n, :name
536636 end
537-
637+
538638 band = Band.new(n: 'Astral Projection')
539639 # => #<Band _id: 5fc1c1ee2c97a64accbeb5e1, name: "Astral Projection">
540-
640+
541641 band.attributes
542642 # => {"_id"=>BSON::ObjectId('5fc1c1ee2c97a64accbeb5e1'), "name"=>"Astral Projection"}
543-
643+
544644 band.n
545645 # => "Astral Projection"
546646
@@ -565,11 +665,11 @@ This is useful for storing different values in ``id`` and ``_id`` fields:
565665
566666 class Band
567667 include Mongoid::Document
568-
668+
569669 unalias_attribute :id
570670 field :id, type: String
571671 end
572-
672+
573673 Band.new(id: '42')
574674 # => #<Band _id: 5fc1c3f42c97a6590684046c, id: "42">
575675
@@ -697,19 +797,19 @@ getter as follows:
697797
698798 class DistanceMeasurement
699799 include Mongoid::Document
700-
800+
701801 field :value, type: Float
702802 field :unit, type: String
703-
803+
704804 def unit
705805 read_attribute(:unit) || "m"
706806 end
707-
807+
708808 def to_s
709809 "#{value} #{unit}"
710810 end
711811 end
712-
812+
713813 measurement = DistanceMeasurement.new(value: 2)
714814 measurement.to_s
715815 # => "2.0 m"
@@ -723,18 +823,18 @@ may be implemented as follows:
723823
724824 class DistanceMeasurement
725825 include Mongoid::Document
726-
826+
727827 field :value, type: Float
728828 field :unit, type: String
729-
829+
730830 def unit=(value)
731831 if value.blank?
732832 value = nil
733833 end
734834 write_attribute(:unit, value)
735835 end
736836 end
737-
837+
738838 measurement = DistanceMeasurement.new(value: 2, unit: "")
739839 measurement.attributes
740840 # => {"_id"=>BSON::ObjectId('613fa15aa15d5d617216104c'), "value"=>2.0, "unit"=>nil}
0 commit comments