diff --git a/docs/reference/fields.txt b/docs/reference/fields.txt index c8105c503e..2debb92b01 100644 --- a/docs/reference/fields.txt +++ b/docs/reference/fields.txt @@ -1057,6 +1057,17 @@ can use in our model class as follows: field :location, type: Point end +You may optionally declare a mapping for the new field type in an initializer: + +.. code-block:: ruby + + # in /config/initializers/mongoid_custom_fields.rb + + Mongoid.configure do |config| + config.field_type :point, Point + end + + Then make a Ruby class to represent the type. This class must define methods used for MongoDB serialization and deserialization as follows: @@ -1235,8 +1246,10 @@ specifiying its handler function as a block: # in /config/initializers/mongoid_custom_fields.rb - Mongoid::Fields.option :max_length do |model, field, value| - model.validates_length_of field.name, maximum: value + Mongoid.configure do |config| + config.field_option :max_length do |model, field, value| + model.validates_length_of field.name, maximum: value + end end Then, use it your model class: diff --git a/docs/release-notes/mongoid-9.0.txt b/docs/release-notes/mongoid-9.0.txt index d06dc22402..5fa5c033ce 100644 --- a/docs/release-notes/mongoid-9.0.txt +++ b/docs/release-notes/mongoid-9.0.txt @@ -241,6 +241,67 @@ defaults to ``true``. When set to false, the older, inconsistent behavior is restored. +Support for Defining Custom Field Type Values +--------------------------------------------- + +Mongoid 9.0 adds the ability to define custom ``field :type`` Symbol values as follows: + +.. code-block:: ruby + + # in /config/initializers/mongoid.rb + + Mongoid.configure do |config| + config.field_type :point, Point + end + +Refer to the :ref:`docs ` for details. + + +Rename error InvalidFieldType to UnknownFieldType +------------------------------------------------- + +The error class InvalidFieldType has been renamed to UnknownFieldType +to improve clarity. This error occurs when attempting using the +``field`` macro in a Document definition with a ``:type`` Symbol that +does not correspond to any built-in or custom-defined field type. + +.. code-block:: ruby + + class User + include Mongoid::Document + + field :name, type: :bogus + #=> raises Mongoid::Errors::UnknownFieldType + end + + +Support for Defining Custom Field Options via Top-Level Config +-------------------------------------------------------------- + +Mongoid 9.0 adds the ability to define custom ``field`` options as follows: + +.. code-block:: ruby + + # in /config/initializers/mongoid.rb + + Mongoid.configure do |config| + config.field_option :max_length do |model, field, value| + model.validates_length_of field.name, maximum: value + end + end + +In Mongoid 8, this was possible with the following legacy syntax. Users are +recommended to migrate to the Mongoid 9.0 syntax above. + +.. code-block:: ruby + + Mongoid::Fields.option :max_length do |model, field, value| + model.validates_length_of field.name, maximum: value + end + +Refer to the :ref:`docs ` for details. + + Bug Fixes and Improvements -------------------------- diff --git a/lib/config/locales/en.yml b/lib/config/locales/en.yml index b5a48cbccb..53a5a46c8d 100644 --- a/lib/config/locales/en.yml +++ b/lib/config/locales/en.yml @@ -237,22 +237,25 @@ en: resolution: "When defining the field :%{name} on '%{klass}', please provide valid options for the field. These are currently: %{valid}. If you meant to define a custom field option, please do so first as follows:\n\n - \_\_Mongoid::Fields.option :%{option} do |model, field, value|\n - \_\_\_\_# Your logic here...\n + \_\_Mongoid.configure do |config|\n + \_\_\_\_config.field_option :%{option} do |model, field, value|\n + \_\_\_\_\_\_# Your logic here...\n + \_\_\_\_end\n \_\_end\n \_\_class %{klass}\n \_\_\_\_include Mongoid::Document\n \_\_\_\_field :%{name}, %{option}: true\n \_\_end\n\n Refer to: - https://www.mongodb.com/docs/mongoid/current/reference/fields/#custom-field-options" - invalid_field_type: - message: "Invalid field type %{type_inspection} for field '%{field}' on model '%{klass}'." - summary: "Model '%{klass}' defines a field '%{field}' with an unknown type value - %{type_inspection}." - resolution: "Please provide a valid type value for the field. + https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-options" + invalid_field_type_definition: + message: "The field type definition of %{type_inspection} to %{klass_inspection} is invalid." + summary: "In the field type definition, either field_type %{type_inspection} is not + a Symbol or String, and/or klass %{klass_inspection} is not a Class or Module." + resolution: "Please ensure you are specifying field_type as either a Symbol or String, + and klass as a Class or Module.\n\n Refer to: - https://www.mongodb.com/docs/mongoid/current/reference/fields/#using-symbols-or-strings-instead-of-classes" + https://www.mongodb.com/docs/mongoid/current/reference/fields/#custom-field-types" invalid_global_executor_concurrency: message: "Invalid global_executor_concurrency option." summary: "You set global_executor_concurrency while async_query_executor @@ -652,6 +655,22 @@ en: resolution: "Define the field '%{name}' in %{klass}, or include Mongoid::Attributes::Dynamic in %{klass} if you intend to store values in fields that are not explicitly defined." + unknown_field_type: + message: "Unknown field type %{type_inspection} for field '%{field}' on model '%{klass}'." + summary: "Model '%{klass}' declares a field '%{field}' with an unknown type value + %{type_inspection}. This value is neither present in Mongoid's default type mapping, + nor defined in a custom field type mapping." + resolution: "Please provide a known type value for the field. If you + meant to define a custom field type, please do so first as follows:\n\n + \_\_Mongoid.configure do |config|\n + \_\_\_\_config.field_type %{type_inspection}, YourTypeClass + \_\_end\n + \_\_class %{klass}\n + \_\_\_\_include Mongoid::Document\n + \_\_\_\_field :%{field}, type: %{type_inspection}\n + \_\_end\n\n + Refer to: + https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-types" unknown_model: message: "Attempted to instantiate an object of the unknown model '%{klass}'." summary: "A document with the value '%{value}' at the key '_type' was used to diff --git a/lib/mongoid/config.rb b/lib/mongoid/config.rb index a4553cfe99..0a2309b318 100644 --- a/lib/mongoid/config.rb +++ b/lib/mongoid/config.rb @@ -367,6 +367,43 @@ def running_with_passenger? @running_with_passenger ||= defined?(PhusionPassenger) end + # Defines a field type mapping, for later use in field :type option. + # + # @example + # Mongoid.configure do |config| + # config.field_type :point, Point + # end + # + # @param [ Symbol | String ] type_name The identifier of the + # defined type. This identifier may be accessible as either a + # Symbol or a String regardless of the type passed to this method. + # @param [ Module ] klass the class of the defined type, which must + # include mongoize, demongoize, and evolve methods. + def field_type(type_name, klass) + Mongoid::Fields::FieldTypes.define_type(type_name, klass) + end + + # Defines an option for the field macro, which runs the handler + # provided as a block. + # + # No assumptions are made about what functionality the handler might + # perform, so it will always be called if the `option_name` key is + # provided in the field definition -- even if it is false or nil. + # + # @example + # Mongoid.configure do |config| + # config.field_option :required do |model, field, value| + # model.validates_presence_of field.name if value + # end + # end + # + # @param [ Symbol ] option_name the option name to match against + # @param [ Proc ] block the handler to execute when the option is + # provided. + def field_option(option_name, &block) + Mongoid::Fields.option(option_name, &block) + end + private def set_log_levels diff --git a/lib/mongoid/errors.rb b/lib/mongoid/errors.rb index e4e6bc6ca2..c1b4ca5b50 100644 --- a/lib/mongoid/errors.rb +++ b/lib/mongoid/errors.rb @@ -18,7 +18,7 @@ require "mongoid/errors/invalid_dependent_strategy" require "mongoid/errors/invalid_field" require "mongoid/errors/invalid_field_option" -require "mongoid/errors/invalid_field_type" +require "mongoid/errors/invalid_field_type_definition" require "mongoid/errors/invalid_find" require "mongoid/errors/invalid_global_executor_concurrency" require "mongoid/errors/invalid_includes" @@ -65,6 +65,7 @@ require "mongoid/errors/transaction_error" require "mongoid/errors/transactions_not_supported" require "mongoid/errors/unknown_attribute" +require "mongoid/errors/unknown_field_type" require "mongoid/errors/unknown_model" require "mongoid/errors/unsaved_document" require "mongoid/errors/unsupported_javascript" diff --git a/lib/mongoid/errors/invalid_field_type_definition.rb b/lib/mongoid/errors/invalid_field_type_definition.rb new file mode 100644 index 0000000000..5e8951f9b3 --- /dev/null +++ b/lib/mongoid/errors/invalid_field_type_definition.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mongoid + module Errors + + # This error is raised when trying to define a field type mapping with + # invalid argument types. + class InvalidFieldTypeDefinition < MongoidError + + # Create the new error. + # + # @example Instantiate the error. + # InvalidFieldTypeDefinition.new('number', 123) + # + # @param [ Object ] field_type The object which is expected to a be Symbol or String. + # @param [ Object ] klass The object which is expected to be a Class or Module. + def initialize(field_type, klass) + type_inspection = field_type.try(:inspect) || field_type.class.inspect + klass_inspection = klass.try(:inspect) || klass.class.inspect + super( + compose_message('invalid_field_type_definition', + type_inspection: type_inspection, klass_inspection: klass_inspection) + ) + end + end + end +end diff --git a/lib/mongoid/errors/invalid_field_type.rb b/lib/mongoid/errors/unknown_field_type.rb similarity index 81% rename from lib/mongoid/errors/invalid_field_type.rb rename to lib/mongoid/errors/unknown_field_type.rb index 6b6c8959b4..921425a230 100644 --- a/lib/mongoid/errors/invalid_field_type.rb +++ b/lib/mongoid/errors/unknown_field_type.rb @@ -5,19 +5,19 @@ module Errors # This error is raised when trying to define a field using a :type option value # that is not present in the field type mapping. - class InvalidFieldType < MongoidError + class UnknownFieldType < MongoidError # Create the new error. # # @example Instantiate the error. - # InvalidFieldType.new('Person', 'first_name', 'stringgy') + # UnknownFieldType.new('Person', 'first_name', 'stringgy') # # @param [ String ] klass The model class. # @param [ String ] field The field on which the invalid type is used. # @param [ Symbol | String ] type The value of the field :type option. def initialize(klass, field, type) super( - compose_message('invalid_field_type', + compose_message('unknown_field_type', klass: klass, field: field, type_inspection: type.inspect) ) end diff --git a/lib/mongoid/fields.rb b/lib/mongoid/fields.rb index 7c81b6880b..02ac8a8431 100644 --- a/lib/mongoid/fields.rb +++ b/lib/mongoid/fields.rb @@ -4,6 +4,7 @@ require "mongoid/fields/foreign_key" require "mongoid/fields/localized" require "mongoid/fields/validators" +require "mongoid/fields/field_types" module Mongoid @@ -14,26 +15,8 @@ module Fields StringifiedSymbol = Mongoid::StringifiedSymbol Boolean = Mongoid::Boolean - # For fields defined with symbols use the correct class. - TYPE_MAPPINGS = { - array: Array, - big_decimal: BigDecimal, - binary: BSON::Binary, - boolean: Mongoid::Boolean, - date: Date, - date_time: DateTime, - float: Float, - hash: Hash, - integer: Integer, - object_id: BSON::ObjectId, - range: Range, - regexp: Regexp, - set: Set, - string: String, - stringified_symbol: StringifiedSymbol, - symbol: Symbol, - time: Time - }.with_indifferent_access + # @deprecated + TYPE_MAPPINGS = ::Mongoid::Fields::FieldTypes::DEFAULT_MAPPING # Constant for all names of the _id field in a document. # @@ -45,7 +28,7 @@ module Fields # BSON classes that are not supported as field types # # @api private - INVALID_BSON_CLASSES = [ BSON::Decimal128, BSON::Int32, BSON::Int64 ].freeze + UNSUPPORTED_BSON_TYPES = [ BSON::Decimal128, BSON::Int32, BSON::Int64 ].freeze module ClassMethods # Returns the list of id fields for this model class, as both strings @@ -283,7 +266,7 @@ class << self # # @example # Mongoid::Fields.option :required do |model, field, value| - # model.validates_presence_of field if value + # model.validates_presence_of field.name if value # end # # @param [ Symbol ] option_name the option name to match against @@ -807,48 +790,37 @@ def field_for(name, options) # Get the class for the given type. # - # @param [ Symbol ] name The name of the field. - # @param [ Symbol | Class ] type The type of the field. + # @param [ Symbol ] field_name The name of the field. + # @param [ Symbol | Class ] raw_type The type of the field. # # @return [ Class ] The type of the field. # - # @raises [ Mongoid::Errors::InvalidFieldType ] if given an invalid field + # @raises [ Mongoid::Errors::UnknownFieldType ] if given an invalid field # type. # # @api private - def retrieve_and_validate_type(name, type) - type_mapping = TYPE_MAPPINGS[type] - result = type_mapping || unmapped_type(type) - if !result.is_a?(Class) - raise Errors::InvalidFieldType.new(self, name, type) - else - if INVALID_BSON_CLASSES.include?(result) - warn_message = "Using #{result} as the field type is not supported. " - if result == BSON::Decimal128 - warn_message += "In BSON <= 4, the BSON::Decimal128 type will work as expected for both storing and querying, but will return a BigDecimal on query in BSON 5+." - else - warn_message += "Saving values of this type to the database will work as expected, however, querying them will return a value of the native Ruby Integer type." - end - Mongoid.logger.warn(warn_message) - end - end - result + def get_field_type(field_name, raw_type) + type = raw_type ? Fields::FieldTypes.get(raw_type) : Object + raise Mongoid::Errors::UnknownFieldType.new(self.name, field_name, raw_type) unless type + warn_if_unsupported_bson_type(type) + type end - # Returns the type of the field if the type was not in the TYPE_MAPPINGS - # hash. + # Logs a warning message if the given type cannot be represented + # by BSON. # - # @param [ Symbol | Class ] type The type of the field. - # - # @return [ Class ] The type of the field. + # @param [ Class ] type The type of the field. # # @api private - def unmapped_type(type) - if "Boolean" == type.to_s - Mongoid::Boolean + def warn_if_unsupported_bson_type(type) + return unless UNSUPPORTED_BSON_TYPES.include?(type) + warn_message = "Using #{type} as the field type is not supported. " + if type == BSON::Decimal128 + warn_message += "In BSON <= 4, the BSON::Decimal128 type will work as expected for both storing and querying, but will return a BigDecimal on query in BSON 5+." else - type || Object + warn_message += "Saving values of this type to the database will work as expected, however, querying them will return a value of the native Ruby Integer type." end + Mongoid.logger.warn(warn_message) end end end diff --git a/lib/mongoid/fields/field_types.rb b/lib/mongoid/fields/field_types.rb new file mode 100644 index 0000000000..57d692859b --- /dev/null +++ b/lib/mongoid/fields/field_types.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +module Mongoid + module Fields + + # Singleton module which contains a mapping of field types to field class. + # Custom field types can be configured. + # + # @api private + module FieldTypes + + # The default mapping of field type symbol/string identifiers to classes. + # + # @api private + DEFAULT_MAPPING = { + array: Array, + big_decimal: BigDecimal, + binary: BSON::Binary, + boolean: Mongoid::Boolean, + date: Date, + date_time: DateTime, + #decimal128: BSON::Decimal128, + float: Float, + hash: Hash, + integer: Integer, + #object: Object, + object_id: BSON::ObjectId, + range: Range, + regexp: Regexp, + set: Set, + string: String, + stringified_symbol: Mongoid::StringifiedSymbol, + symbol: Symbol, + time: Time, + #time_with_zone: ActiveSupport::TimeWithZone, + }.with_indifferent_access.freeze + + class << self + + # Resolves the user-provided field type to the field type class. + # + # @example + # Mongoid::FieldTypes.get(:point) + # + # @param [ Module | Symbol | String ] field_type The field + # type class or its string or symbol identifier. + # + # @return [ Module | nil ] The underlying field type class, or nil if + # string or symbol was passed and it is not mapped to any class. + def get(field_type) + case field_type + when Module + module_field_type(field_type) + when Symbol, String + mapping[field_type] + end + end + + # Defines a field type mapping, for later use in field :type option. + # + # @example + # Mongoid::FieldTypes.define_type(:point, Point) + # + # @param [ Symbol | String ] field_type The identifier of the + # defined type. This identifier may be accessible as either a + # Symbol or a String regardless of the type passed to this method. + # @param [ Class ] klass the class of the defined type, which must + # include mongoize, demongoize, and evolve methods. + def define_type(field_type, klass) + unless (field_type.is_a?(String) || field_type.is_a?(Symbol)) && klass.is_a?(Module) + raise Mongoid::Errors::InvalidFieldTypeDefinition.new(field_type, klass) + end + mapping[field_type] = klass + end + + delegate :delete, to: :mapping + + # The memoized mapping of field type definitions to classes. + # + # @return [ ActiveSupport::HashWithIndifferentAccess ] The memoized field mapping. + def mapping + @mapping ||= DEFAULT_MAPPING.dup + end + + private + + def module_field_type(field_type) + return Mongoid::Boolean if field_type.to_s == "Boolean" + field_type + end + end + end + end +end diff --git a/spec/mongoid/config_spec.rb b/spec/mongoid/config_spec.rb index 463b8c6475..8fe06d3384 100644 --- a/spec/mongoid/config_spec.rb +++ b/spec/mongoid/config_spec.rb @@ -911,4 +911,56 @@ end end end + + context '#field_type' do + around do |example| + klass = Mongoid::Fields::FieldTypes + klass.instance_variable_set(:@mapping, klass::DEFAULT_MAPPING.dup) + example.run + klass.instance_variable_set(:@mapping, klass::DEFAULT_MAPPING.dup) + end + + it 'can define a custom type' do + Mongoid.configure do |config| + config.field_type :my_type, Integer + end + + expect(Mongoid::Fields::FieldTypes.get(:my_type)).to eq Integer + end + + it 'can override and existing type' do + Mongoid.configure do |config| + config.field_type :integer, String + end + + expect(Mongoid::Fields::FieldTypes.get(:integer)).to eq String + end + end + + context '#field_option method' do + after do + Mongoid::Fields.instance_variable_set(:@options, {}) + end + + it 'can define a custom field option' do + Mongoid.configure do |config| + config.field_option :my_required do |model, field, value| + model.validates_presence_of field.name if value + end + end + + klass = Class.new do + include Mongoid::Document + field :my_field, my_required: true + + def self.model_name + OpenStruct.new(human: 'Klass') + end + end + + instance = klass.new + expect(instance.valid?).to eq false + expect(instance.errors.full_messages).to eq ["My field can't be blank"] + end + end end diff --git a/spec/mongoid/errors/invalid_field_type_definition_spec.rb b/spec/mongoid/errors/invalid_field_type_definition_spec.rb new file mode 100644 index 0000000000..887fe32412 --- /dev/null +++ b/spec/mongoid/errors/invalid_field_type_definition_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Mongoid::Errors::InvalidFieldTypeDefinition do + + describe "#message" do + + context 'when field_type is the wrong type' do + let(:error) do + described_class.new(123, Integer) + end + + it "contains the problem in the message" do + expect(error.message).to include( + "The field type definition of 123 to Integer is invalid." + ) + end + + it "contains the summary in the message" do + expect(error.message).to include( + "In the field type definition, either field_type 123 is not a Symbol" + ) + end + + it "contains the resolution in the message" do + expect(error.message).to include( + 'Please ensure you are specifying field_type as either a Symbol' + ) + end + end + + context 'when klass is the wrong type' do + let(:error) do + described_class.new('number', 123) + end + + it "contains the problem in the message" do + expect(error.message).to include( + 'The field type definition of "number" to 123 is invalid.' + ) + end + + it "contains the summary in the message" do + expect(error.message).to include( + 'In the field type definition, either field_type "number" is not a Symbol' + ) + end + + it "contains the resolution in the message" do + expect(error.message).to include( + 'Please ensure you are specifying field_type as either a Symbol' + ) + end + end + end +end diff --git a/spec/mongoid/errors/invalid_field_type_spec.rb b/spec/mongoid/errors/unknown_field_type_spec.rb similarity index 63% rename from spec/mongoid/errors/invalid_field_type_spec.rb rename to spec/mongoid/errors/unknown_field_type_spec.rb index f903f5d50b..7fdac53e14 100644 --- a/spec/mongoid/errors/invalid_field_type_spec.rb +++ b/spec/mongoid/errors/unknown_field_type_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -describe Mongoid::Errors::InvalidFieldType do +describe Mongoid::Errors::UnknownFieldType do describe "#message" do @@ -17,13 +17,19 @@ it "contains the problem in the message" do expect(error.message).to include( - "Invalid field type :stringgy for field 'first_name' on model 'Person'." + "Unknown field type :stringgy for field 'first_name' on model 'Person'." ) end it "contains the summary in the message" do expect(error.message).to include( - "Model 'Person' defines a field 'first_name' with an unknown type value :stringgy." + "Model 'Person' declares a field 'first_name' with an unknown type value :stringgy." + ) + end + + it "contains the resolution in the message" do + expect(error.message).to include( + 'Please provide a known type value for the field.' ) end end @@ -35,20 +41,20 @@ it "contains the problem in the message" do expect(error.message).to include( - %q,Invalid field type "stringgy" for field 'first_name' on model 'Person'., + %q,Unknown field type "stringgy" for field 'first_name' on model 'Person'., ) end it "contains the summary in the message" do expect(error.message).to include( - %q,Model 'Person' defines a field 'first_name' with an unknown type value "stringgy"., + %q,Model 'Person' declares a field 'first_name' with an unknown type value "stringgy"., ) end end it "contains the resolution in the message" do expect(error.message).to include( - 'Please provide a valid type value for the field.' + 'Please provide a known type value for the field.' ) end end diff --git a/spec/mongoid/fields/field_types_spec.rb b/spec/mongoid/fields/field_types_spec.rb new file mode 100644 index 0000000000..47c4e8f89a --- /dev/null +++ b/spec/mongoid/fields/field_types_spec.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Mongoid::Fields::FieldTypes do + + around do |example| + described_class.instance_variable_set(:@mapping, described_class::DEFAULT_MAPPING.dup) + example.run + described_class.instance_variable_set(:@mapping, described_class::DEFAULT_MAPPING.dup) + end + + describe '.get' do + subject { described_class.get(type) } + + context 'when value is a default mapped symbol' do + let(:type) { :float } + + it 'uses the default mapped type' do + is_expected.to eq Float + end + end + + context 'when value is a default mapped string' do + let(:type) { 'float' } + + it 'uses the default mapped type' do + is_expected.to eq Float + end + end + + context 'when value is a custom mapped symbol' do + before { described_class.define_type('number', Integer) } + let(:type) { :number } + + it 'uses the custom mapped type' do + is_expected.to eq Integer + end + end + + context 'when value is a custom mapped string' do + before { described_class.define_type(:number, Float) } + let(:type) { 'number' } + + it 'uses the custom mapped type' do + is_expected.to eq Float + end + end + + context 'when value is an unmapped symbol' do + let(:type) { :my_value } + + it 'returns nil' do + is_expected.to eq nil + end + end + + context 'when value is a unmapped string' do + let(:type) { 'my_value' } + + it 'returns nil' do + is_expected.to eq nil + end + end + + context 'when value is a module' do + let(:type) { String } + + it 'uses the module type' do + is_expected.to eq String + end + end + + context 'when value is the module Boolean' do + let(:type) do + stub_const('Boolean', Module.new) + Boolean + end + + it 'returns Mongoid::Boolean type' do + is_expected.to eq Mongoid::Boolean + end + end + + context 'when value is nil' do + let(:type) { nil } + + it 'returns nil' do + is_expected.to eq nil + end + end + end + + describe '.define' do + + it 'can define a new type' do + described_class.define_type(:my_string, String) + expect(described_class.get(:my_string)).to eq String + end + + it 'can override a default type' do + described_class.define_type(:integer, String) + expect(described_class.get(:integer)).to eq String + end + + it 'does not alter the DEFAULT_MAPPING constant' do + described_class.define_type(:integer, String) + expect(described_class::DEFAULT_MAPPING[:integer]).to eq Integer + end + end + + describe '.delete' do + + it 'can delete a custom type' do + described_class.define_type(:my_string, String) + expect(described_class.get(:my_string)).to eq String + described_class.delete('my_string') + expect(described_class.get(:my_string)).to eq nil + end + + it 'can delete a default type' do + described_class.delete(:integer) + expect(described_class.get(:integer)).to eq nil + end + + it 'does not alter the DEFAULT_MAPPING constant' do + described_class.delete(:integer) + expect(described_class::DEFAULT_MAPPING[:integer]).to eq Integer + end + end + + describe '.mapping' do + + it 'returns the default mapping by default' do + expect(described_class.mapping).to eq described_class::DEFAULT_MAPPING + end + + it 'can add a type' do + described_class.define_type(:my_string, String) + expect(described_class.mapping[:my_string]).to eq(String) + end + + it 'can delete a default type' do + described_class.delete(:integer) + expect(described_class.mapping).to_not have_key(:integer) + end + end +end diff --git a/spec/mongoid/fields_spec.rb b/spec/mongoid/fields_spec.rb index b32dd306e4..4bbcd6bee4 100644 --- a/spec/mongoid/fields_spec.rb +++ b/spec/mongoid/fields_spec.rb @@ -327,83 +327,51 @@ end end - it "converts :array to Array" do - expect(klass.field(:test, type: :array).type).to be(Array) - end - - it "converts :big_decimal to BigDecimal" do - expect(klass.field(:test, type: :big_decimal).type).to be(BigDecimal) - end - - it "converts :binary to BSON::Binary" do - expect(klass.field(:test, type: :binary).type).to be(BSON::Binary) - end - - it "converts :boolean to Mongoid::Boolean" do - expect(klass.field(:test, type: :boolean).type).to be(Mongoid::Boolean) - end - - it "converts :date to Date" do - expect(klass.field(:test, type: :date).type).to be(Date) - end - - it "converts :date_time to DateTime" do - expect(klass.field(:test, type: :date_time).type).to be(DateTime) - end - - it "converts :float to Float" do - expect(klass.field(:test, type: :float).type).to be(Float) - end - - it "converts :hash to Hash" do - expect(klass.field(:test, type: :hash).type).to be(Hash) - end - - it "converts :integer to Integer" do - expect(klass.field(:test, type: :integer).type).to be(Integer) - end - - it "converts :object_id to BSON::ObjectId" do - expect(klass.field(:test, type: :object_id).type).to be(BSON::ObjectId) - end - - it "converts :range to Range" do - expect(klass.field(:test, type: :range).type).to be(Range) - end - - it "converts :regexp to Rexegp" do - expect(klass.field(:test, type: :regexp).type).to be(Regexp) - end - - it "converts :set to Set" do - expect(klass.field(:test, type: :set).type).to be(Set) - end - - it "converts :string to String" do - expect(klass.field(:test, type: :string).type).to be(String) - end - - it "converts :symbol to Symbol" do - expect(klass.field(:test, type: :symbol).type).to be(Symbol) - end - - it "converts :time to Time" do - expect(klass.field(:test, type: :time).type).to be(Time) + { + array: Array, + big_decimal: BigDecimal, + binary: BSON::Binary, + boolean: Mongoid::Boolean, + date: Date, + date_time: DateTime, + #decimal128: BSON::Decimal128, + float: Float, + hash: Hash, + integer: Integer, + #object: Object, + object_id: BSON::ObjectId, + range: Range, + regexp: Regexp, + set: Set, + string: String, + stringified_symbol: Mongoid::StringifiedSymbol, + symbol: Symbol, + time: Time, + #time_with_zone: ActiveSupport::TimeWithZone, + }.each do |field_type, field_klass| + + it "converts Symbol :#{field_type} to #{field_klass}" do + expect(klass.field(:test, type: field_type).type).to be(field_klass) + end + + it "converts String \"#{field_type}\" to #{field_klass}" do + expect(klass.field(:test, type: field_type.to_s).type).to be(field_klass) + end end context 'when using an unknown symbol' do - it 'raises InvalidFieldType' do + it 'raises UnknownFieldType' do lambda do - klass.field(:test, type: :bogus) - end.should raise_error(Mongoid::Errors::InvalidFieldType, /defines a field 'test' with an unknown type value :bogus/) + klass.field(:test, type: :bogus) + end.should raise_error(Mongoid::Errors::UnknownFieldType, /declares a field 'test' with an unknown type value :bogus/) end end context 'when using an unknown string' do - it 'raises InvalidFieldType' do + it 'raises UnknownFieldType' do lambda do - klass.field(:test, type: 'bogus') - end.should raise_error(Mongoid::Errors::InvalidFieldType, /defines a field 'test' with an unknown type value "bogus"/) + klass.field(:test, type: 'bogus') + end.should raise_error(Mongoid::Errors::UnknownFieldType, /declares a field 'test' with an unknown type value "bogus"/) end end end @@ -1973,6 +1941,12 @@ class DiscriminatorChild2 < DiscriminatorParent end end + describe '::TYPE_MAPPINGS' do + it 'returns the default mapping' do + expect(described_class::TYPE_MAPPINGS).to eq ::Mongoid::Fields::FieldTypes::DEFAULT_MAPPING + end + end + describe "localize: :present" do let(:product) do