Skip to content

Commit 86707f9

Browse files
authored
MONGOID-5351 Implement Dots and Dollars in Mongoid (#5272)
* MONGOID-5300 fix dotted getter and add integration tests * MONGOID-5300 add docs * MONGOID-5351 add ProhibitedSetter error * MONGOID-5351 add ProhibitedSetter to docs * MONGOID-5351 import error and remove read_only stuff * MONGOID-5351 prohibit updates of dots and dollar fields * MONGOID-5351 correct language * MONGOID-5351 condition on driver 2.18 * Update docs/reference/fields.txt * MONGOID-5351 answer comments * MONGOID-5351 add api privates
1 parent 0f0a118 commit 86707f9

File tree

7 files changed

+390
-6
lines changed

7 files changed

+390
-6
lines changed

docs/reference/fields.txt

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,8 +1415,47 @@ requiring the usage of the aggregation pipeline for both queries and updates,
14151415
applications should avoid using dots in field names and starting field names
14161416
with the dollar sign if possible.
14171417

1418-
The Ruby driver `currently prohibits
1419-
<https://jira.mongodb.org/browse/RUBY-2528>`_ inserting documents whose
1420-
field names contain dots or begin with the dollar sign. However, if such
1421-
documents are inserted using other software, Mongoid and the Ruby driver
1422-
provide limited support for retrieving and operating on these documents.
1418+
Mongoid, starting in version 8, now allows users to access fields that begin with
1419+
dollar signs and that contain dots/periods. They can be accessed using the ``send``
1420+
method as follows:
1421+
1422+
.. code::
1423+
1424+
class User
1425+
include Mongoid::Document
1426+
field :"first.last", type: String
1427+
field :"$_amount", type: Integer
1428+
end
1429+
1430+
user = User.first
1431+
user.send(:"first.last")
1432+
# => Mike.Trout
1433+
user.send(:"$_amount")
1434+
# => 42650000
1435+
1436+
It is also possible to use ``read_attribute`` to access these fields:
1437+
1438+
.. code::
1439+
1440+
user.read_attribute("first.last")
1441+
# => Mike.Trout
1442+
1443+
Due to `server limitations <https://www.mongodb.com/docs/manual/core/dot-dollar-considerations/>`_,
1444+
updating and replacing fields containing dots and dollars requires using special
1445+
operators. For this reason, calling setters on these fields is prohibited and
1446+
will raise an error:
1447+
1448+
.. code::
1449+
1450+
class User
1451+
include Mongoid::Document
1452+
field :"first.last", type: String
1453+
field :"$_amount", type: Integer
1454+
end
1455+
1456+
user = User.new
1457+
user.send(:"first.last=", "Shohei.Ohtani")
1458+
# raises a InvalidDotDollarAssignment error
1459+
user.send(:"$_amount=", 8500000)
1460+
# raises a InvalidDotDollarAssignment error
1461+

lib/config/locales/en.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,17 @@ en:
132132
allowed."
133133
resolution: "Try setting the discriminator key on %{superclass} or one of %{class_name}'s
134134
ancestors."
135+
invalid_dot_dollar_assignment:
136+
message: "Cannot set the %{attr} attribute on the %{klass} class."
137+
summary: "Calling the setters for fields that start with a dollar
138+
sign ($) or contain a dot/period (.) is prohibited. See the
139+
Mongoid documentation on Field Names with Dots/Periods (.) and
140+
Dollar Signs ($) for more information."
141+
resolution: "In order to set fields that start with a dollar
142+
sign ($) or contain a dot/period (.), the aggregation pipeline can
143+
be used. MongoDB provides specific operators for working with these
144+
fields. Refer to the MongoDB documentation here:
145+
https://www.mongodb.com/docs/manual/core/dot-dollar-considerations/"
135146
invalid_elem_match_operator:
136147
message: "Invalid $elemMatch operator '%{operator}'."
137148
summary: "You misspelled an operator or are using an operator that

lib/mongoid/attributes.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def read_attribute_before_type_cast(name)
123123
# @raise [ Errors::ReadonlyAttribute ] If the field cannot be removed due
124124
# to being flagged as reaodnly.
125125
def remove_attribute(name)
126+
validate_writable_field_name!(name.to_s)
126127
as_writable_attribute!(name) do |access|
127128
_assigning do
128129
attribute_will_change!(access)
@@ -145,6 +146,8 @@ def remove_attribute(name)
145146
# @param [ String, Symbol ] name The name of the attribute to update.
146147
# @param [ Object ] value The value to set for the attribute.
147148
def write_attribute(name, value)
149+
validate_writable_field_name!(name.to_s)
150+
148151
field_name = database_field_name(name)
149152

150153
if attribute_missing?(field_name)
@@ -273,7 +276,11 @@ def read_raw_attribute(name)
273276
end
274277

275278
if hash_dot_syntax?(normalized)
276-
attributes.__nested__(normalized)
279+
if fields.key?(normalized)
280+
attributes[normalized]
281+
else
282+
attributes.__nested__(normalized)
283+
end
277284
else
278285
attributes[normalized]
279286
end

lib/mongoid/errors.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
require "mongoid/errors/invalid_query"
2525
# Must be after invalid_query.
2626
require "mongoid/errors/invalid_discriminator_key_target"
27+
require "mongoid/errors/invalid_dot_dollar_assignment"
2728
require "mongoid/errors/invalid_elem_match_operator"
2829
require "mongoid/errors/invalid_estimated_count_criteria"
2930
require "mongoid/errors/invalid_expression_operator"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
module Errors
5+
6+
# This error is raised when trying to use the setter for a field that starts
7+
# with a dollar sign ($) or contains a dot/period (.).
8+
class InvalidDotDollarAssignment < MongoidError
9+
10+
# Create the new error.
11+
#
12+
# @param [ Class ] klass The class of the document.
13+
# @param [ Class ] attr The attribute attempted to be written.
14+
#
15+
# @api private
16+
def initialize(klass, attr)
17+
super(
18+
compose_message("invalid_dot_dollar_assignment", { klass: klass, attr: attr })
19+
)
20+
end
21+
end
22+
end
23+
end

lib/mongoid/fields.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,32 @@ def using_object_ids?
246246
self.class.using_object_ids?
247247
end
248248

249+
# Does this field start with a dollar sign ($) or contain a dot/period (.)?
250+
#
251+
# @api private
252+
#
253+
# @param [ String ] name The field name.
254+
#
255+
# @return [ true, false ] If this field is dotted or dollared.
256+
def dot_dollar_field?(name)
257+
n = aliased_fields[name] || name
258+
fields.key?(n) && (n.include?('.') || n.start_with?('$'))
259+
end
260+
261+
# Validate whether or not the field starts with a dollar sign ($) or
262+
# contains a dot/period (.).
263+
#
264+
# @api private
265+
#
266+
# @raise [ InvalidDotDollarAssignment ] If contains dots or starts with a dollar.
267+
#
268+
# @param [ String ] name The field name.
269+
def validate_writable_field_name!(name)
270+
if dot_dollar_field?(name)
271+
raise Errors::InvalidDotDollarAssignment.new(self.class, name)
272+
end
273+
end
274+
249275
class << self
250276

251277
# Stores the provided block to be run when the option name specified is

0 commit comments

Comments
 (0)