Skip to content

Commit b14e0ba

Browse files
Merge branch 'master' into custom-field-type-support
2 parents 0049e29 + 0a65cc0 commit b14e0ba

File tree

33 files changed

+1968
-400
lines changed

33 files changed

+1968
-400
lines changed

.evergreen/config.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,10 @@ axes:
492492
display_name: "Driver-min (JRuby)"
493493
variables:
494494
DRIVER: "min-jruby"
495+
- id: bson-min
496+
display_name: "BSON-min"
497+
variables:
498+
DRIVER: "bson-min"
495499
- id: "rails"
496500
display_name: Rails Version
497501
values:
@@ -663,7 +667,19 @@ buildvariants:
663667
run_on:
664668
- rhel70-small
665669
tasks:
666-
- name: "test"
670+
- name: "test"
671+
672+
- matrix_name: "bson-min"
673+
matrix_spec:
674+
driver: [min]
675+
ruby: ["ruby-2.7"]
676+
mongodb-version: "5.0"
677+
topology: "standalone"
678+
display_name: "${ruby}, ${driver}, ${mongodb-version}, ${topology}"
679+
run_on:
680+
- ubuntu1804-small
681+
tasks:
682+
- name: "test"
667683

668684
- matrix_name: "rails-6"
669685
matrix_spec:

.evergreen/run-tests.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ elif test "$DRIVER" = "oldstable"; then
6666
elif test "$DRIVER" = "min"; then
6767
bundle install --gemfile=gemfiles/driver_min.gemfile
6868
BUNDLE_GEMFILE=gemfiles/driver_min.gemfile
69+
elif test "$DRIVER" = "bson-min"; then
70+
bundle install --gemfile=gemfiles/bson_min.gemfile
71+
BUNDLE_GEMFILE=gemfiles/bson_min.gemfile
6972
elif test "$DRIVER" = "stable-jruby"; then
7073
bundle install --gemfile=gemfiles/driver_stable_jruby.gemfile
7174
BUNDLE_GEMFILE=gemfiles/driver_stable_jruby.gemfile
@@ -104,7 +107,7 @@ elif test -n "$APP_TESTS"; then
104107
bash $HOME/n stable
105108
export PATH=$HOME/.n/bin:$PATH
106109
npm -g install yarn
107-
110+
108111
bundle exec rspec spec/integration/app_spec.rb
109112
else
110113
bundle exec rake ci

docs/reference/configuration.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ for details on driver options.
280280
# Ruby on Rails logger instance. (default: :info)
281281
log_level: :info
282282

283+
# When using the BigDecimal field type, store the value in the database
284+
# as a BSON::Decimal128 instead of a string. (default: false)
285+
map_big_decimal_to_decimal128: false
286+
283287
# Preload all models in development, needed when models use
284288
# inheritance. (default: false)
285289
preload_models: false

docs/reference/fields.txt

Lines changed: 115 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ You can safely omit type specifications when:
115115

116116
Types 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
422421
This is because the meaning of ``$`` is different between PCRE and Ruby
423422
regular 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}

gemfiles/bson_min.gemfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
source "https://rubygems.org"
2+
gemspec path: '..'
3+
4+
gem 'bson', '4.14.0'
5+
# This configuration doesn't require a specific driver version. When bson-4.14.0
6+
# was released, current driver version was 2.17.0
7+
gem 'mongo', '2.17.0'
8+
9+
require_relative './standard'
10+
11+
standard_dependencies

lib/mongoid.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
require 'mongo/active_support'
1717

1818
require "mongoid/version"
19+
require "mongoid/deprecable"
1920
require "mongoid/config"
2021
require "mongoid/persistence_context"
2122
require "mongoid/loggable"

lib/mongoid/config.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ module Config
7474
# Return stored times as UTC.
7575
option :use_utc, default: false
7676

77+
# Store BigDecimals as Decimal128s instead of strings in the db.
78+
option :map_big_decimal_to_decimal128, default: false
79+
7780
# Has Mongoid been configured? This is checking that at least a valid
7881
# client config exists.
7982
#

lib/mongoid/contextual/mongo.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class Mongo
1717
include Association::EagerLoadable
1818
include Queryable
1919

20+
Mongoid.deprecate(self, :geo_near)
21+
2022
# Options constant.
2123
OPTIONS = [ :hint,
2224
:limit,

lib/mongoid/criteria/queryable/extensions/big_decimal.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@ module ClassMethods
2323
# @return [ String ] The big decimal as a string.
2424
def evolve(object)
2525
__evolve__(object) do |obj|
26-
obj ? obj.to_s : obj
26+
if obj
27+
if obj.is_a?(::BigDecimal) && Mongoid.map_big_decimal_to_decimal128
28+
BSON::Decimal128.new(obj)
29+
elsif obj.is_a?(BSON::Decimal128)
30+
obj
31+
else
32+
obj.to_s
33+
end
34+
end
2735
end
2836
end
2937
end

lib/mongoid/criteria/queryable/selectable.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ module Queryable
1010
module Selectable
1111
extend Macroable
1212

13+
Mongoid.deprecate(self, :geo_spacial)
14+
1315
# Constant for a LineString $geometry.
1416
LINE_STRING = "LineString"
1517

0 commit comments

Comments
 (0)