Skip to content

Commit e9d314b

Browse files
committed
Proof of concept of evolve raw value and "strict" field option
1 parent 94fdc7d commit e9d314b

File tree

25 files changed

+219
-58
lines changed

25 files changed

+219
-58
lines changed

docs/release-notes/mongoid-8.1.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,21 @@ from the ``_translations`` hash:
116116

117117
See the section on :ref:`Localize :present Field Option <present-fields>` for
118118
more details on how these are used.
119+
120+
121+
Support for Passing Raw Values into Queries
122+
-------------------------------------------
123+
124+
When performing queries, it is now possible skip Mongoid's type coercion logic
125+
using the ``Mongoid::RawValue`` wrapper class. This can be useful when legacy
126+
data in the database is of a different type than the field definition.
127+
128+
.. code-block:: ruby
129+
130+
class Person
131+
include Mongoid::Document
132+
field :age, type: Integer
133+
end
134+
135+
# Query for the string "42", not the integer 42
136+
Person.where(age: Mongoid::RawValue("42"))

lib/mongoid/attributes.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ def write_attribute(name, value)
170170

171171
if attribute_writable?(field_name)
172172
_assigning do
173+
# TODO: remove this
174+
# validate_attribute_value(field_name, value)
173175
localized = fields[field_name].try(:localized?)
174176
attributes_before_type_cast[name.to_s] = value
175177
typed_value = typed_value_for(field_name, value)
@@ -358,6 +360,31 @@ def unalias_attribute(name)
358360

359361
private
360362

363+
# Validates an attribute value as being assignable to the specified field.
364+
#
365+
# For now, only Hash and Array fields are validated, and the value is
366+
# being checked to be of an appropriate type (i.e. either Hash or Array,
367+
# respectively, or nil).
368+
#
369+
# This method takes the name of the field as stored in the document
370+
# in the database, not (necessarily) the Ruby method name used to read/write
371+
# the said field.
372+
#
373+
# @param [ String, Symbol ] field_name The name of the field.
374+
# @param [ Object ] value The value to be validated.
375+
# TODO: remove this
376+
# def validate_attribute_value(field_name, value)
377+
# return if value.nil?
378+
# field = fields[field_name]
379+
# return unless field
380+
# validatable_types = [ Hash, Array ]
381+
# if validatable_types.include?(field.type)
382+
# unless value.is_a?(field.type)
383+
# raise Mongoid::Errors::InvalidValue.new(field.type, value.class)
384+
# end
385+
# end
386+
# end
387+
361388
def lookup_attribute_presence(name, value)
362389
if localized_fields.has_key?(name) && value
363390
value = localized_fields[name].send(:lookup, value)

lib/mongoid/config.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ module Config
7070
# existing method.
7171
option :scope_overwrite_exception, default: false
7272

73+
# Indicates whether or not to raise an error when attempting
74+
# to assign an incompatible type to a field.
75+
option :strict_type_assignment, default: false
76+
77+
# Indicates whether uncastable values from the database should
78+
# be returned wrapped by Mongoid::RawValue class.
79+
option :wrap_uncastable_values_from_database, default: false
80+
7381
# Use ActiveSupport's time zone in time operations instead of the
7482
# Ruby default time zone.
7583
option :use_activesupport_time_zone, default: true

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def evolve(object)
137137
when ::Array, ::Set
138138
object.map { |obj| obj.class.evolve(obj) }
139139
else
140-
object
140+
Mongoid::RawValue(object, 'Array')
141141
end
142142
end
143143
end

lib/mongoid/criteria/queryable/selector.rb

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,20 @@ def evolve_multi(specs)
150150
#
151151
# @return [ Object ] The serialized object.
152152
def evolve(serializer, value)
153-
case value
154-
when Hash
155-
evolve_hash(serializer, value)
156-
when Array
157-
evolve_array(serializer, value)
158-
when Range
159-
value.__evolve_range__(serializer: serializer)
160-
else
161-
(serializer || value.class).evolve(value)
162-
end
153+
_value = case value
154+
when Hash
155+
evolve_hash(serializer, value)
156+
when Array
157+
evolve_array(serializer, value)
158+
when Range
159+
value.__evolve_range__(serializer: serializer)
160+
when Mongoid::RawValue
161+
value
162+
else
163+
(serializer || value.class).evolve(value)
164+
end
165+
_value = _value.raw_value if _value.is_a?(Mongoid::RawValue)
166+
_value
163167
end
164168

165169
# Evolve a single key selection with array values.

lib/mongoid/extensions.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def transform_keys
4848
require "mongoid/extensions/object"
4949
require "mongoid/extensions/object_id"
5050
require "mongoid/extensions/range"
51+
require "mongoid/raw_value"
5152
require "mongoid/extensions/regexp"
5253
require "mongoid/extensions/set"
5354
require "mongoid/extensions/string"

lib/mongoid/extensions/array.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ def mongoize(object)
150150
case object
151151
when ::Array, ::Set
152152
object.map(&:mongoize)
153+
else
154+
Mongoid::RawValue(object, 'Array')
153155
end
154156
end
155157

lib/mongoid/extensions/big_decimal.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,14 @@ def mongoize(object)
7272
BSON::Decimal128.new(object)
7373
elsif object.numeric?
7474
BSON::Decimal128.new(object.to_s)
75+
else
76+
Mongoid::RawValue(object, 'BigDecimal')
7577
end
7678
else
7779
if object.is_a?(BSON::Decimal128) || object.numeric?
7880
object.to_s
81+
else
82+
Mongoid::RawValue(object, 'BigDecimal')
7983
end
8084
end
8185
end

lib/mongoid/extensions/binary.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def mongoize(object)
3030
case object
3131
when BSON::Binary then object
3232
when String, Symbol then BSON::Binary.new(object.to_s)
33+
else Mongoid::RawValue(object, 'BSON::Binary')
3334
end
3435
end
3536
alias :demongoize :mongoize

lib/mongoid/extensions/boolean.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ def mongoize(object)
1818
true
1919
elsif object.to_s =~ (/\A(false|f|no|n|off|0|0.0)\z/i)
2020
false
21+
else
22+
Mongoid::RawValue(object, 'Boolean')
2123
end
2224
end
2325
alias :demongoize :mongoize

0 commit comments

Comments
 (0)