Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEEDS FEEDBACK] MONGOID-5382 RawValue for mongoize/demongoize #5368

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions lib/mongoid/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ def write_attribute(name, value)

if attribute_writable?(field_name)
_assigning do
# TODO: remove this
# validate_attribute_value(field_name, value)
localized = fields[field_name].try(:localized?)
attributes_before_type_cast[name.to_s] = value
typed_value = typed_value_for(field_name, value)
Expand Down Expand Up @@ -360,6 +362,31 @@ def unalias_attribute(name)

private

# Validates an attribute value as being assignable to the specified field.
#
# For now, only Hash and Array fields are validated, and the value is
# being checked to be of an appropriate type (i.e. either Hash or Array,
# respectively, or nil).
#
# This method takes the name of the field as stored in the document
# in the database, not (necessarily) the Ruby method name used to read/write
# the said field.
#
# @param [ String, Symbol ] field_name The name of the field.
# @param [ Object ] value The value to be validated.
# TODO: remove this
# def validate_attribute_value(field_name, value)
# return if value.nil?
# field = fields[field_name]
# return unless field
# validatable_types = [ Hash, Array ]
# if validatable_types.include?(field.type)
# unless value.is_a?(field.type)
# raise Mongoid::Errors::InvalidValue.new(field.type, value.class)
# end
# end
# end

def lookup_attribute_presence(name, value)
if localized_fields.has_key?(name) && value
value = localized_fields[name].send(:lookup, value)
Expand Down
12 changes: 12 additions & 0 deletions lib/mongoid/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ module Config
# existing method.
option :scope_overwrite_exception, default: false

# Indicates whether or not to raise an error when attempting
# to assign an incompatible type to a field.
option :raise_invalid_type_assignment_error, default: false

# Indicates whether uncastable values from the database should
# be returned wrapped by Mongoid::RawValue class.
option :wrap_uncastable_values_from_database, default: false

# Use ActiveSupport's time zone in time operations instead of the
# Ruby default time zone.
option :use_activesupport_time_zone, default: true

# Return stored times as UTC.
option :use_utc, default: false

Expand Down
2 changes: 1 addition & 1 deletion lib/mongoid/criteria/queryable/extensions/array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def evolve(object)
when ::Array, ::Set
object.map { |obj| obj.class.evolve(obj) }
else
object
Mongoid::RawValue(object, 'Array')
end
end
end
Expand Down
28 changes: 17 additions & 11 deletions lib/mongoid/criteria/queryable/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,24 @@ def evolve_multi(specs)
#
# @return [ Object ] The serialized object.
def evolve(serializer, value)
case value
when Mongoid::RawValue
value.raw_value
when Hash
evolve_hash(serializer, value)
when Array
evolve_array(serializer, value)
when Range
value.__evolve_range__(serializer: serializer)
else
(serializer || value.class).evolve(value)
_value = case value
when Mongoid::RawValue
value.raw_value
when Hash
evolve_hash(serializer, value)
when Array
evolve_array(serializer, value)
when Range
value.__evolve_range__(serializer: serializer)
else
(serializer || value.class).evolve(value)
end

while _value.is_a?(Mongoid::RawValue) do
_value = _value.raw_value
end

_value
end

# Evolve a single key selection with array values.
Expand Down
2 changes: 2 additions & 0 deletions lib/mongoid/extensions/array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ def mongoize(object)
case object
when ::Array, ::Set
object.map(&:mongoize)
else
Mongoid::RawValue(object, 'Array')
end
end

Expand Down
12 changes: 8 additions & 4 deletions lib/mongoid/extensions/big_decimal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,18 @@ def mongoize(object)
BSON::Decimal128.new(object)
elsif object.numeric?
BSON::Decimal128.new(object.to_s)
elsif !object.is_a?(String)
object.try(:to_d)
elsif !object.is_a?(String) && object.respond_to?(:to_d)
object.to_d
else
Mongoid::RawValue(object, 'BigDecimal')
end
else
if object.is_a?(BSON::Decimal128) || object.numeric?
object.to_s
elsif !object.is_a?(String)
object.try(:to_d)&.to_s
elsif !object.is_a?(String) && object.respond_to?(:to_d)
object.to_d
else
Mongoid::RawValue(object, 'BigDecimal')
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/extensions/binary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def mongoize(object)
case object
when BSON::Binary then object
when String, Symbol then BSON::Binary.new(object.to_s)
else Mongoid::RawValue(object, 'BSON::Binary')
end
end
alias :demongoize :mongoize
Expand Down
2 changes: 2 additions & 0 deletions lib/mongoid/extensions/boolean.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def mongoize(object)
true
elsif object.to_s =~ (/\A(false|f|no|n|off|0|0.0)\z/i)
false
else
Mongoid::RawValue(object, 'Boolean')
end
end
alias :demongoize :mongoize
Expand Down
9 changes: 5 additions & 4 deletions lib/mongoid/extensions/date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,13 @@ def mongoize(object)
else
time = object.__mongoize_time__
end

if time.acts_like?(:time)
return ::Time.utc(time.year, time.month, time.day)
end
rescue ArgumentError
nil
end
if time.acts_like?(:time)
::Time.utc(time.year, time.month, time.day)
end
Mongoid::RawValue(object, 'Date')
end
end
end
Expand Down
4 changes: 3 additions & 1 deletion lib/mongoid/extensions/float.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ def mongoize(object)
if object.numeric?
object.to_f
end
elsif object.respond_to?(:to_f)
object.to_f
else
object.try(:to_f)
Mongoid::RawValue(object, 'Float')
end
end
alias :demongoize :mongoize
Expand Down
2 changes: 2 additions & 0 deletions lib/mongoid/extensions/hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ def mongoize(object)
object.dup.transform_values!(&:mongoize)
when Hash
BSON::Document.new(object.transform_values(&:mongoize))
else
Mongoid::RawValue(object, 'Hash')
end
end

Expand Down
4 changes: 3 additions & 1 deletion lib/mongoid/extensions/integer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ def mongoize(object)
if object.numeric?
object.to_i
end
elsif object.respond_to?(:to_i)
object.to_i
else
object.try(:to_i)
Mongoid::RawValue(object, 'Integer')
end
end
alias :demongoize :mongoize
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/extensions/range.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def mongoize(object)
case object
when Hash then __mongoize_hash__(object)
when Range then __mongoize_range__(object)
else Mongoid::RawValue(object, 'Range')
end
end

Expand Down
60 changes: 54 additions & 6 deletions lib/mongoid/extensions/raw_value.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
# frozen_string_literal: true
# rubocop:todo all

# Wrapper class used when a value cannot be casted in evolve method.
# Wrapper class used when a value cannot be casted by the
# mongoize, demongoize, and evolve methods.
module Mongoid

# Instantiates a new Mongoid::RawValue object. Used as a syntax shortcut.
# Instantiates a new Mongoid::RawValue object. Used as a
# syntax shortcut.
#
# @example Create a Mongoid::RawValue object.
# Mongoid::RawValue("Beagle")
#
# @param [ Object ] raw_value The underlying raw object.
# @param [ String ] cast_class_name The name of the class
# to which the raw value is intended to be cast.
#
# @return [ Mongoid::RawValue ] The object.
def RawValue(*args)
RawValue.new(*args)
def RawValue(raw_value, cast_class_name = nil)
RawValue.new(raw_value, cast_class_name)
end

# Represents a value which cannot be type-casted between Ruby and MongoDB.
class RawValue

attr_reader :raw_value
attr_reader :raw_value,
:cast_class_name

def initialize(raw_value)
# Instantiates a new Mongoid::RawValue object.
#
# @example Create a Mongoid::RawValue object.
# Mongoid::RawValue.new("Beagle", "String")
#
# @param [ Object ] raw_value The underlying raw object.
# @param [ String ] cast_class_name The name of the class
# to which the raw value is intended to be cast.
#
# @return [ Mongoid::RawValue ] The object.
def initialize(raw_value, cast_class_name = nil)
@raw_value = raw_value
@cast_class_name = cast_class_name
end

# Returns a string containing a human-readable representation of
Expand All @@ -30,5 +48,35 @@ def initialize(raw_value)
def inspect
"RawValue: #{raw_value.inspect}"
end

# Raises a Mongoid::Errors::InvalidValue error.
def raise_error!
raise Mongoid::Errors::InvalidValue.new(raw_value.class.name, cast_class_name)
end

# Logs a warning that a value cannot be cast.
def warn
Mongoid.logger.warn("Cannot cast #{raw_value.class.name} to #{cast_class_name}; returning nil")
end

# Delegate all missing methods to the raw value.
#
# @param [ String, Symbol ] method_name The name of the method.
# @param [ Array ] args The arguments passed to the method.
#
# @return [ Object ] The method response.
ruby2_keywords def method_missing(method_name, *args, &block)
raw_value.send(method_name, *args, &block)
end

# Delegate all missing methods to the raw value.
#
# @param [ String, Symbol ] method_name The name of the method.
# @param [ true | false ] include_private Whether to check private methods.
#
# @return [ true | false ] Whether the raw value object responds to the method.
def respond_to_missing?(method_name, include_private = false)
raw_value.respond_to?(method_name, include_private)
end
end
end
15 changes: 9 additions & 6 deletions lib/mongoid/extensions/regexp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ module ClassMethods
# @return [ Regexp | nil ] The object mongoized or nil.
def mongoize(object)
return if object.nil?
case object
when String then ::Regexp.new(object)
when ::Regexp then object
when BSON::Regexp::Raw then object.compile
begin
_object = case object
when String then ::Regexp.new(object)
when ::Regexp then object
when BSON::Regexp::Raw then object.compile
end
return _object if _object
rescue RegexpError
end
rescue RegexpError
nil
Mongoid::RawValue(object, 'Regexp')
end
alias :demongoize :mongoize
end
Expand Down
1 change: 1 addition & 0 deletions lib/mongoid/extensions/set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def mongoize(object)
case object
when ::Set then ::Array.mongoize(object.to_a).uniq
when ::Array then ::Array.mongoize(object).uniq
else Mongoid::RawValue(object, 'Set')
end
end
end
Expand Down
4 changes: 3 additions & 1 deletion lib/mongoid/extensions/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ module ClassMethods
#
# @return [ String ] The object mongoized.
def mongoize(object)
object.try(:to_s)
return if object.nil?
return object.to_s if object.respond_to?(:to_s)
Mongoid::RawValue.new(object, 'String')
end
alias :demongoize :mongoize
end
Expand Down
4 changes: 3 additions & 1 deletion lib/mongoid/extensions/symbol.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ module ClassMethods
#
# @return [ Symbol | nil ] The object mongoized or nil.
def mongoize(object)
object.try(:to_sym)
return if object.nil?
return object.to_sym if object.respond_to?(:to_sym)
Mongoid::RawValue.new(object, 'Symbol')
end
alias :demongoize :mongoize
end
Expand Down
21 changes: 10 additions & 11 deletions lib/mongoid/extensions/time.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,18 @@ def mongoize(object)
return if object.blank?
begin
time = object.__mongoize_time__
rescue ArgumentError
return
end

if time.acts_like?(:time)
if object.respond_to?(:sec_fraction)
::Time.at(time.to_i, object.sec_fraction * 10**6).utc
elsif time.respond_to?(:subsec)
::Time.at(time.to_i, time.subsec * 10**6).utc
else
::Time.at(time.to_i, time.usec).utc
if time.acts_like?(:time)
if object.respond_to?(:sec_fraction)
return ::Time.at(time.to_i, object.sec_fraction * 10**6).utc
elsif time.respond_to?(:subsec)
return ::Time.at(time.to_i, time.subsec * 10**6).utc
else
return ::Time.at(time.to_i, time.usec).utc
end
end
rescue ArgumentError
end
Mongoid::RawValue.new(object, 'Time')
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/mongoid/fields/localized.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def localize_present?
#
# @return [ Hash ] The locale with string translation.
def mongoize(object)
{ ::I18n.locale.to_s => type.mongoize(object) }
{ ::I18n.locale.to_s => super(object) }
end

private
Expand Down
Loading
Loading