diff --git a/.rubocop.yml b/.rubocop.yml index fbd3c4fe8..38ba68884 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -215,6 +215,9 @@ Naming/PredicateName: - has_attribute? - has_attribute_before_type_cast? +RSpec/ImplicitSubject: + Enabled: false + RSpec/IndexedLet: Enabled: false diff --git a/docs/reference/fields.txt b/docs/reference/fields.txt index e7a2b4496..59143dbfa 100644 --- a/docs/reference/fields.txt +++ b/docs/reference/fields.txt @@ -1016,13 +1016,24 @@ First, declare the new field type mapping in an initializer: .. code-block:: ruby - # in /config/initializers/mongoid_custom_fields.rb + # in /config/initializers/active_document_custom_fields.rb ActiveDocument::Fields.configure do type :point, Point end +You may optionally declare a mapping for the new field type in an initializer: + +.. code-block:: ruby + + # in /config/initializers/active_document_custom_fields.rb + + ActiveDocument.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: @@ -1201,8 +1212,10 @@ specifying its handler function as a block: # in /config/initializers/active_document_custom_fields.rb - ActiveDocument::Fields.option :max_length do |model, field, value| - model.validates_length_of field.name, maximum: value + ActiveDocument.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/migrating-from-mongoid.txt b/docs/release-notes/migrating-from-mongoid.txt index ed6552aa2..3a97a8a9a 100644 --- a/docs/release-notes/migrating-from-mongoid.txt +++ b/docs/release-notes/migrating-from-mongoid.txt @@ -27,10 +27,71 @@ Mongoid 8.0 adds the ability to define custom ``field :type`` Symbol values as f .. code-block:: ruby - # in /config/initializers/mongoid_custom_fields.rb + # in /config/initializers/active_document_custom_fields.rb Mongoid.configure do |config| config.field_type :point, Point end Refer to the :ref:`docs ` for details. + + +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/active_document.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/active_document.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. diff --git a/lib/active_document/config.rb b/lib/active_document/config.rb index e3175ba46..3ee8e0d65 100644 --- a/lib/active_document/config.rb +++ b/lib/active_document/config.rb @@ -373,6 +373,43 @@ def running_with_passenger? @running_with_passenger ||= defined?(PhusionPassenger) end + # Defines a field type mapping, for later use in field :type option. + # + # @example + # ActiveDocument.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) + ActiveDocument::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 + # ActiveDocument.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) + ActiveDocument::Fields.option(option_name, &block) + end + ActiveDocument.deprecate(self, :running_with_passenger?) private diff --git a/lib/active_document/errors.rb b/lib/active_document/errors.rb index da1ff786b..7de2ecbc5 100644 --- a/lib/active_document/errors.rb +++ b/lib/active_document/errors.rb @@ -19,7 +19,7 @@ require 'active_document/errors/invalid_dependent_strategy' require 'active_document/errors/invalid_field' require 'active_document/errors/invalid_field_option' -require 'active_document/errors/invalid_field_type' +require 'active_document/errors/invalid_field_type_definition' require 'active_document/errors/invalid_find' require 'active_document/errors/invalid_global_executor_concurrency' require 'active_document/errors/invalid_includes' @@ -66,6 +66,7 @@ require 'active_document/errors/transaction_error' require 'active_document/errors/transactions_not_supported' require 'active_document/errors/unknown_attribute' +require 'active_document/errors/unknown_field_type' require 'active_document/errors/unknown_model' require 'active_document/errors/unsaved_document' require 'active_document/errors/unsupported_javascript' diff --git a/lib/active_document/errors/invalid_field_type_definition.rb b/lib/active_document/errors/invalid_field_type_definition.rb new file mode 100644 index 000000000..e26d3280c --- /dev/null +++ b/lib/active_document/errors/invalid_field_type_definition.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActiveDocument + module Errors + + # This error is raised when trying to define a field type mapping with + # invalid argument types. + class InvalidFieldTypeDefinition < BaseError + + # 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/active_document/errors/invalid_field_type.rb b/lib/active_document/errors/unknown_field_type.rb similarity index 81% rename from lib/active_document/errors/invalid_field_type.rb rename to lib/active_document/errors/unknown_field_type.rb index c84949290..d3e3ff738 100644 --- a/lib/active_document/errors/invalid_field_type.rb +++ b/lib/active_document/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 < BaseError + class UnknownFieldType < BaseError # 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/active_document/fields.rb b/lib/active_document/fields.rb index ff4494b03..11f4b3422 100644 --- a/lib/active_document/fields.rb +++ b/lib/active_document/fields.rb @@ -26,7 +26,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 @@ -282,10 +282,8 @@ def type(symbol, klass) # provided in the field definition -- even if it is false or nil. # # @example - # ActiveDocument::Fields.configure do - # option :required do |model, field, value| - # model.validates_presence_of field.name if value - # end + # ActiveDocument::Fields.option :required do |model, field, value| + # model.validates_presence_of field.name if value # end # # @param [ Symbol ] option_name the option name to match against @@ -805,69 +803,39 @@ 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. # - # @raise [ ActiveDocument::Errors::InvalidFieldType ] if given an invalid field + # @raises [ ActiveDocument::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) - elsif unsupported_type?(result) - warn_message = "Using #{result} as the field type is not supported. " - warn_message += if result == BSON::Decimal128 - '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 - '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 - ActiveDocument.logger.warn(warn_message) - end + def get_field_type(field_name, raw_type) + type = raw_type ? Fields::FieldTypes.get(raw_type) : Object + raise ActiveDocument::Errors::UnknownFieldType.new(name, field_name, raw_type) unless type - result + warn_if_unsupported_bson_type(type) + type end - def field_type_klass_for(field, type) - klass = Fields::FieldTypes.get(type) - return klass if klass - - raise ActiveDocument::Errors::InvalidFieldType.new(name, field, type) - end - - # Returns the type of the field if the type was not in the TYPE_MAPPINGS - # hash. - # - # @param [ Symbol | Class ] type The type of the field. + # Logs a warning message if the given type cannot be represented + # by BSON. # - # @return [ Class ] The type of the field. + # @param [ Class ] type The type of the field. # # @api private - def unmapped_type(type) - if type.to_s == 'Boolean' - ActiveDocument::Boolean - else - type || Object - end - end - - # Queries whether or not the given type is permitted as a declared field - # type. - # - # @param [ Class ] type The type to query - # - # @return [ true | false ] whether or not the type is supported - # - # @api private - def unsupported_type?(type) - return !ActiveDocument::Config.allow_bson5_decimal128? if type == BSON::Decimal128 - - INVALID_BSON_CLASSES.include?(type) + 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. " + warn_message += if type == BSON::Decimal128 + '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 + '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 + ActiveDocument.logger.warn(warn_message) end end end diff --git a/lib/active_document/fields/field_types.rb b/lib/active_document/fields/field_types.rb index 00e049571..e116a25cf 100644 --- a/lib/active_document/fields/field_types.rb +++ b/lib/active_document/fields/field_types.rb @@ -3,12 +3,16 @@ module ActiveDocument module Fields - # Singleton module which contains a cache for field type definitions. + # Singleton module which contains a mapping of field types to field class. # Custom field types can be configured. + # + # @api private module FieldTypes extend self - # For fields defined with symbols use the correct class. + # The default mapping of field type symbol/string identifiers to classes. + # + # @api private DEFAULT_MAPPING = { array: Array, bigdecimal: BigDecimal, @@ -18,7 +22,6 @@ module FieldTypes date: Date, datetime: DateTime, date_time: DateTime, - decimal128: BSON::Decimal128, double: Float, float: Float, hash: Hash, @@ -32,53 +35,82 @@ module FieldTypes stringified_symbol: ActiveDocument::StringifiedSymbol, symbol: Symbol, time: Time, - time_with_zone: ActiveSupport::TimeWithZone + undefined: Object }.with_indifferent_access.freeze - def get(value) - value = value.to_sym if value.is_a?(String) - mapping[value] || handle_unmapped_type(value) - end + class << self - def define(symbol, klass) - mapping[symbol.to_sym] = klass - end + # Resolves the user-provided field type to the field type class. + # + # @example + # ActiveDocument::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 - delegate :delete, to: :mapping + # Defines a field type mapping, for later use in field :type option. + # + # @example + # ActiveDocument::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 ActiveDocument::Errors::InvalidFieldTypeDefinition.new(field_type, klass) + end - private + mapping[field_type] = klass + end - def mapping - @mapping ||= DEFAULT_MAPPING.dup - 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 - def handle_unmapped_type(type) - return Object if type.nil? + private - if type.is_a?(Module) - warn_class_type(type.name) - return ActiveDocument::Boolean if type.to_s == 'Boolean' + def module_field_type(field_type) + warn_class_type(field_type) + return ActiveDocument::Boolean if field_type.to_s == 'Boolean' - return type + field_type end - nil - end - - def warn_class_type(type) - return if warned_class_types.include?(type) + def warn_class_type(type) + type = type.name + return if warned_class_types.include?(type) - symbol = type.demodulize.underscore - ActiveDocument.logger.warn( - "Using a Class (#{type}) in the field :type option is deprecated " \ - 'and will be removed in a future major ActiveDocument version. ' \ - "Please use a Symbol (:#{symbol}) instead." - ) - warned_class_types << type - end + symbol = type.demodulize.underscore + ActiveDocument.logger.warn( + "Using a Class (#{type}) in the field :type option is deprecated " \ + 'and will be removed in a future major ActiveDocument version. ' \ + "Please use a Symbol (:#{symbol}) instead." + ) + warned_class_types << type + end - def warned_class_types - @warned_class_types ||= [] + def warned_class_types + @warned_class_types ||= [] + end end end end diff --git a/lib/config/locales/en.yml b/lib/config/locales/en.yml index 637f4f2e1..5fd63a0a8 100644 --- a/lib/config/locales/en.yml +++ b/lib/config/locales/en.yml @@ -254,8 +254,8 @@ 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 - \_\_ActiveDocument::Fields.configure do\n - \_\_\_\_option :%{option} do |model, field, value|\n + \_\_ActiveDocument.configure do |config|\n + \_\_\_\_config.field_option :%{option} do |model, field, value|\n \_\_\_\_\_\_# Your logic here...\n \_\_\_\_end\n \_\_end\n @@ -265,20 +265,12 @@ en: \_\_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}. This value is neither present in ActiveDocument's default type mapping, - nor defined in a custom field type mapping." - resolution: "Please provide a valid :type value for the field. If you - meant to define a custom field type, please do so first as follows:\n\n - \_\_ActiveDocument::Fields.configure do\n - \_\_\_\_type %{type_inspection}, YourTypeClass\n - \_\_end\n - \_\_class %{klass}\n - \_\_\_\_include ActiveDocument::Document\n - \_\_\_\_field :%{field}, type: %{type_inspection}\n - \_\_end\n\n + 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/#custom-field-types" invalid_global_executor_concurrency: @@ -680,6 +672,22 @@ en: resolution: "Define the field '%{name}' in %{klass}, or include ActiveDocument::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 ActiveDocument'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 + \_\_ActiveDocument.configure do |config|\n + \_\_\_\_config.field_type %{type_inspection}, YourTypeClass + \_\_end\n + \_\_class %{klass}\n + \_\_\_\_include ActiveDocument::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/spec/active_document/config/environment_spec.rb b/spec/active_document/config/environment_spec.rb index dd487b790..16ccd569c 100644 --- a/spec/active_document/config/environment_spec.rb +++ b/spec/active_document/config/environment_spec.rb @@ -115,7 +115,7 @@ context 'when environment not specified' do it 'uses the rails environment' do - expect(subject).to eq('clients' => ['test']) + is_expected.to eq('clients' => ['test']) end end @@ -123,7 +123,7 @@ let(:environment) { 'development' } it 'uses the specified environment' do - expect(subject).to eq('clients' => ['dev']) + is_expected.to eq('clients' => ['dev']) end end @@ -163,7 +163,7 @@ end it 'loads successfully' do - expect(subject).to be_a(Hash) + is_expected.to be_a(Hash) expect(subject.fetch('clients').fetch('default').fetch('options').fetch('auto_encryption_options').fetch('schema_map')).to be_a(Hash) end end diff --git a/spec/active_document/config_spec.rb b/spec/active_document/config_spec.rb index 9e18bd3ad..937dc6c24 100644 --- a/spec/active_document/config_spec.rb +++ b/spec/active_document/config_spec.rb @@ -825,6 +825,58 @@ end end + describe '#field_type' do + around do |example| + klass = ActiveDocument::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 + ActiveDocument.configure do |config| + config.field_type :my_type, Integer + end + + expect(ActiveDocument::Fields::FieldTypes.get(:my_type)).to eq Integer + end + + it 'can override and existing type' do + ActiveDocument.configure do |config| + config.field_type :integer, String + end + + expect(ActiveDocument::Fields::FieldTypes.get(:integer)).to eq String + end + end + + describe '#field_option method' do + after do + ActiveDocument::Fields.instance_variable_set(:@options, {}) + end + + it 'can define a custom field option' do + ActiveDocument.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 ActiveDocument::Document + field :my_field, my_required: true + + def self.model_name + OpenStruct.new(human: 'Klass') + end + end + + instance = klass.new + expect(instance.valid?).to be false + expect(instance.errors.full_messages).to eq ["My field can't be blank"] + end + end + describe 'deprecations' do {}.each do |option, default| diff --git a/spec/active_document/contextual/aggregable/memory_spec.rb b/spec/active_document/contextual/aggregable/memory_spec.rb index 82d29e202..21cb6365e 100644 --- a/spec/active_document/contextual/aggregable/memory_spec.rb +++ b/spec/active_document/contextual/aggregable/memory_spec.rb @@ -19,7 +19,7 @@ end it do - expect(subject).to eq('count' => 0, 'avg' => nil, 'max' => nil, 'min' => nil, 'sum' => 0) + is_expected.to eq('count' => 0, 'avg' => nil, 'max' => nil, 'min' => nil, 'sum' => 0) end end @@ -39,7 +39,7 @@ end it do - expect(subject).to eq('count' => 0, 'avg' => 750.0, 'max' => 1000, 'min' => 500, 'sum' => 1500) + is_expected.to eq('count' => 0, 'avg' => 750.0, 'max' => 1000, 'min' => 500, 'sum' => 1500) end end end diff --git a/spec/active_document/contextual/mongo/documents_loader_spec.rb b/spec/active_document/contextual/mongo/documents_loader_spec.rb index b9958e238..df4198150 100644 --- a/spec/active_document/contextual/mongo/documents_loader_spec.rb +++ b/spec/active_document/contextual/mongo/documents_loader_spec.rb @@ -44,16 +44,16 @@ describe '#initialize' do it 'initializes in pending state' do - expect(subject).to be_pending - expect(subject).to_not be_started + is_expected.to be_pending + is_expected.to_not be_started end end describe '#unschedule' do it 'changes state' do subject.unschedule - expect(subject).to_not be_pending - expect(subject).to_not be_started + is_expected.to_not be_pending + is_expected.to_not be_started end end @@ -74,8 +74,8 @@ it 'changes the state to started' do subject.wait! - expect(subject).to be_started - expect(subject).to_not be_pending + is_expected.to be_started + is_expected.to_not be_pending end end end diff --git a/spec/active_document/criteria/queryable/extensions/range_spec.rb b/spec/active_document/criteria/queryable/extensions/range_spec.rb index b0844e620..6587449f2 100644 --- a/spec/active_document/criteria/queryable/extensions/range_spec.rb +++ b/spec/active_document/criteria/queryable/extensions/range_spec.rb @@ -22,7 +22,7 @@ let(:expected_max) { Time.utc(2010, 1, 3, 0, 0, 0, 0) } it 'returns a selection of times' do - expect(subject).to eq('$gte' => expected_min, '$lte' => expected_max) + is_expected.to eq('$gte' => expected_min, '$lte' => expected_max) end end @@ -33,7 +33,7 @@ let(:expected_max) { Time.utc(2010, 1, 3, 0, 0, 0, 0) } it 'returns a selection of times' do - expect(subject).to eq('$gte' => expected_min, '$lte' => expected_max) + is_expected.to eq('$gte' => expected_min, '$lte' => expected_max) end end @@ -44,7 +44,7 @@ let(:max) { max_time.to_f } it 'returns a selection of times' do - expect(subject).to eq('$gte' => min_time, '$lte' => max_time) + is_expected.to eq('$gte' => min_time, '$lte' => max_time) end end @@ -55,7 +55,7 @@ let(:max) { max_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$gte' => min_time, '$lte' => max_time) + is_expected.to eq('$gte' => min_time, '$lte' => max_time) end end @@ -68,7 +68,7 @@ let(:max) { max_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$gte' => min_time, '$lt' => max_time) + is_expected.to eq('$gte' => min_time, '$lt' => max_time) end end @@ -79,7 +79,7 @@ let(:min) { min_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$gte' => min_time) + is_expected.to eq('$gte' => min_time) end end @@ -90,7 +90,7 @@ let(:max) { max_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$lte' => max_time) + is_expected.to eq('$lte' => max_time) end end @@ -101,7 +101,7 @@ let(:max) { max_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$lt' => max_time) + is_expected.to eq('$lt' => max_time) end end end @@ -119,7 +119,7 @@ let(:expected_max) { Time.new(2010, 1, 3, 12, 0, 0).utc } it 'returns a selection of times' do - expect(subject).to eq('$gte' => expected_min, '$lte' => expected_max) + is_expected.to eq('$gte' => expected_min, '$lte' => expected_max) end it 'returns the times in utc' do @@ -132,7 +132,7 @@ let(:max) { Time.new(2010, 1, 3, 12, 0, 0).to_s } it 'returns a selection of times' do - expect(subject).to eq('$gte' => min.to_time, '$lte' => max.to_time) + is_expected.to eq('$gte' => min.to_time, '$lte' => max.to_time) end it 'returns the times in utc' do @@ -147,7 +147,7 @@ let(:expected_max) { Time.at(max).utc } it 'returns a selection of times' do - expect(subject).to eq('$gte' => expected_min, '$lte' => expected_max) + is_expected.to eq('$gte' => expected_min, '$lte' => expected_max) end it 'returns the times in utc' do @@ -162,7 +162,7 @@ let(:expected_max) { Time.at(max).utc } it 'returns a selection of times' do - expect(subject).to eq('$gte' => expected_min, '$lte' => expected_max) + is_expected.to eq('$gte' => expected_min, '$lte' => expected_max) end it 'returns the times in utc' do @@ -179,7 +179,7 @@ let(:max) { max_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$gte' => min_time, '$lt' => max_time) + is_expected.to eq('$gte' => min_time, '$lt' => max_time) end end @@ -190,7 +190,7 @@ let(:min) { min_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$gte' => min_time) + is_expected.to eq('$gte' => min_time) end end @@ -201,7 +201,7 @@ let(:max) { max_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$lte' => max_time) + is_expected.to eq('$lte' => max_time) end end @@ -212,7 +212,7 @@ let(:max) { max_time.to_i } it 'returns a selection of times' do - expect(subject).to eq('$lt' => max_time) + is_expected.to eq('$lt' => max_time) end end end @@ -224,7 +224,7 @@ let(:range) { 1..3 } it 'returns the inclusive range criterion' do - expect(subject).to eq('$gte' => 1, '$lte' => 3) + is_expected.to eq('$gte' => 1, '$lte' => 3) end end @@ -232,7 +232,7 @@ let(:range) { 1...3 } it 'returns the non inclusive range criterion' do - expect(subject).to eq('$gte' => 1, '$lt' => 3) + is_expected.to eq('$gte' => 1, '$lt' => 3) end end @@ -240,7 +240,7 @@ let(:range) { 1.. } it 'returns the endless range criterion' do - expect(subject).to eq('$gte' => 1) + is_expected.to eq('$gte' => 1) end end @@ -248,7 +248,7 @@ let(:range) { 1... } it 'returns the endless range criterion' do - expect(subject).to eq('$gte' => 1) + is_expected.to eq('$gte' => 1) end end @@ -256,7 +256,7 @@ let(:range) { ..1 } it 'returns the endless range criterion' do - expect(subject).to eq('$lte' => 1) + is_expected.to eq('$lte' => 1) end end @@ -264,7 +264,7 @@ let(:range) { ...1 } it 'returns the endless range criterion' do - expect(subject).to eq('$lt' => 1) + is_expected.to eq('$lt' => 1) end end @@ -272,7 +272,7 @@ let(:range) { 'a'..'z' } it 'returns the character range' do - expect(subject).to eq('$gte' => 'a', '$lte' => 'z') + is_expected.to eq('$gte' => 'a', '$lte' => 'z') end end end @@ -281,7 +281,7 @@ let(:range) { 1.3..4.5 } it 'returns the range as Time objects' do - expect(subject).to eq('$gte' => 1.3, '$lte' => 4.5) + is_expected.to eq('$gte' => 1.3, '$lte' => 4.5) end end @@ -289,7 +289,7 @@ let(:range) { Date.new(2010, 1, 1)..Date.new(2010, 1, 3) } it 'returns the range as Time objects' do - expect(subject).to eq({ '$gte' => Time.utc(2010, 1, 1, 0, 0, 0, 0), '$lte' => Time.utc(2010, 1, 3, 0, 0, 0, 0) }) + is_expected.to eq({ '$gte' => Time.utc(2010, 1, 1, 0, 0, 0, 0), '$lte' => Time.utc(2010, 1, 3, 0, 0, 0, 0) }) expect(subject['$gte'].utc?).to be(true) expect(subject['$lte'].utc?).to be(true) end @@ -299,7 +299,7 @@ let(:range) { Time.at(0)..Time.at(1) } it 'returns the range as Time objects' do - expect(subject).to eq({ '$gte' => Time.at(0), '$lte' => Time.at(1) }) + is_expected.to eq({ '$gte' => Time.at(0), '$lte' => Time.at(1) }) expect(subject['$gte'].utc?).to be(true) expect(subject['$lte'].utc?).to be(true) end @@ -309,7 +309,7 @@ let(:range) { Time.at(0).in_time_zone..Time.at(1).in_time_zone } it 'returns the range as Time objects' do - expect(subject).to eq({ '$gte' => Time.at(0), '$lte' => Time.at(1) }) + is_expected.to eq({ '$gte' => Time.at(0), '$lte' => Time.at(1) }) expect(subject['$gte'].utc?).to be(true) expect(subject['$lte'].utc?).to be(true) end @@ -319,7 +319,7 @@ let(:range) { Date.new(2010, 1, 1)..Date.new(2010, 1, 3) } it 'returns the range as Time objects' do - expect(subject).to eq({ '$gte' => Time.utc(2010, 1, 1, 0, 0, 0, 0), '$lte' => Time.utc(2010, 1, 3, 0, 0, 0, 0) }) + is_expected.to eq({ '$gte' => Time.utc(2010, 1, 1, 0, 0, 0, 0), '$lte' => Time.utc(2010, 1, 3, 0, 0, 0, 0) }) expect(subject['$gte'].utc?).to be(true) expect(subject['$lte'].utc?).to be(true) end @@ -329,7 +329,7 @@ let(:range) { Time.at(0)..Date.new(2010, 1, 3) } it 'returns the range as Time objects' do - expect(subject).to eq({ '$gte' => Time.at(0), '$lte' => Time.utc(2010, 1, 3, 0, 0, 0, 0) }) + is_expected.to eq({ '$gte' => Time.at(0), '$lte' => Time.utc(2010, 1, 3, 0, 0, 0, 0) }) expect(subject['$gte'].utc?).to be(true) expect(subject['$lte'].utc?).to be(true) end @@ -339,7 +339,7 @@ let(:range) { Time.at(0).in_time_zone..Time.at(1).in_time_zone } it 'returns the range as Time objects' do - expect(subject).to eq({ '$gte' => Time.at(0), '$lte' => Time.at(1) }) + is_expected.to eq({ '$gte' => Time.at(0), '$lte' => Time.at(1) }) expect(subject['$gte'].utc?).to be(true) expect(subject['$lte'].utc?).to be(true) end diff --git a/spec/active_document/errors/invalid_field_type_definition_spec.rb b/spec/active_document/errors/invalid_field_type_definition_spec.rb new file mode 100644 index 000000000..a772f3220 --- /dev/null +++ b/spec/active_document/errors/invalid_field_type_definition_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ActiveDocument::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/active_document/errors/invalid_field_type_spec.rb b/spec/active_document/errors/unknown_field_type_spec.rb similarity index 96% rename from spec/active_document/errors/invalid_field_type_spec.rb rename to spec/active_document/errors/unknown_field_type_spec.rb index 6a84cfe96..f50b8baab 100644 --- a/spec/active_document/errors/invalid_field_type_spec.rb +++ b/spec/active_document/errors/unknown_field_type_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe ActiveDocument::Errors::InvalidFieldType do +describe ActiveDocument::Errors::UnknownFieldType do describe '#message' do diff --git a/spec/active_document/extensions/range_spec.rb b/spec/active_document/extensions/range_spec.rb index 2d3247be8..584124bab 100644 --- a/spec/active_document/extensions/range_spec.rb +++ b/spec/active_document/extensions/range_spec.rb @@ -11,7 +11,7 @@ let(:hash) { { 'min' => 1, 'max' => 3 } } it 'returns an ascending range' do - expect(subject).to eq(1..3) + is_expected.to eq(1..3) end end @@ -19,7 +19,7 @@ let(:hash) { { 'min' => 1, 'max' => 3, 'exclude_end' => true } } it 'returns an ascending range' do - expect(subject).to eq(1...3) + is_expected.to eq(1...3) end end @@ -27,7 +27,7 @@ let(:hash) { { 'min' => 5, 'max' => 1 } } it 'returns an descending range' do - expect(subject).to eq(5..1) + is_expected.to eq(5..1) end end @@ -35,7 +35,7 @@ let(:hash) { { 'min' => 5, 'max' => 1, 'exclude_end' => true } } it 'returns an descending range' do - expect(subject).to eq(5...1) + is_expected.to eq(5...1) end end @@ -43,7 +43,7 @@ let(:hash) { { 'min' => 'a', 'max' => 'z' } } it 'returns an alphabetic range' do - expect(subject).to eq('a'..'z') + is_expected.to eq('a'..'z') end end @@ -51,7 +51,7 @@ let(:hash) { { 'min' => 'a', 'max' => 'z', 'exclude_end' => true } } it 'returns an alphabetic range' do - expect(subject).to eq('a'...'z') + is_expected.to eq('a'...'z') end end @@ -60,7 +60,7 @@ context 'kernel can support endless range' do it 'returns an alphabetic range' do - expect(subject).to eq(1..) + is_expected.to eq(1..) end end end @@ -70,7 +70,7 @@ context 'kernel can support endless range' do it 'returns an alphabetic range' do - expect(subject).to eq(1...) + is_expected.to eq(1...) end end end @@ -80,7 +80,7 @@ context 'kernel can support beginning-less range' do it 'returns an alphabetic range' do - expect(subject).to eq(nil..3) + is_expected.to eq(nil..3) end end end @@ -90,7 +90,7 @@ context 'kernel can support endless range' do it 'returns an alphabetic beginning-less' do - expect(subject).to eq(...3) + is_expected.to eq(...3) end end end @@ -99,7 +99,7 @@ let(:hash) { { 'min^' => 'a', 'max^' => 'z', 'exclude_end^' => true } } it 'returns nil' do - expect(subject).to be_nil + is_expected.to be_nil end end @@ -107,7 +107,7 @@ let(:hash) { { min: 1, max: 3 } } it 'returns an ascending range' do - expect(subject).to eq(1..3) + is_expected.to eq(1..3) end end end @@ -118,7 +118,7 @@ let(:range) { 1..3 } it 'returns the object hash' do - expect(subject).to eq('min' => 1, 'max' => 3) + is_expected.to eq('min' => 1, 'max' => 3) end end @@ -126,7 +126,7 @@ let(:range) { 1...3 } it 'returns the object hash' do - expect(subject).to eq('min' => 1, 'max' => 3, 'exclude_end' => true) + is_expected.to eq('min' => 1, 'max' => 3, 'exclude_end' => true) end end @@ -134,7 +134,7 @@ let(:range) { 5..1 } it 'returns the object hash' do - expect(subject).to eq('min' => 5, 'max' => 1) + is_expected.to eq('min' => 5, 'max' => 1) end end @@ -142,7 +142,7 @@ let(:range) { 5...1 } it 'returns the object hash' do - expect(subject).to eq('min' => 5, 'max' => 1, 'exclude_end' => true) + is_expected.to eq('min' => 5, 'max' => 1, 'exclude_end' => true) end end @@ -150,7 +150,7 @@ let(:range) { 5.. } it 'returns the object hash' do - expect(subject).to eq('min' => 5) + is_expected.to eq('min' => 5) end end @@ -158,7 +158,7 @@ let(:range) { 5... } it 'returns the object hash' do - expect(subject).to eq('min' => 5, 'exclude_end' => true) + is_expected.to eq('min' => 5, 'exclude_end' => true) end end @@ -166,7 +166,7 @@ let(:range) { ..5 } it 'returns the object hash' do - expect(subject).to eq('max' => 5) + is_expected.to eq('max' => 5) end end @@ -174,7 +174,7 @@ let(:range) { ...5 } it 'returns the object hash' do - expect(subject).to eq('max' => 5, 'exclude_end' => true) + is_expected.to eq('max' => 5, 'exclude_end' => true) end end @@ -182,7 +182,7 @@ let(:range) { 'a'..'z' } it 'returns the object hash' do - expect(subject).to eq('min' => 'a', 'max' => 'z') + is_expected.to eq('min' => 'a', 'max' => 'z') end end @@ -190,7 +190,7 @@ let(:range) { 'a'...'z' } it 'returns the object hash' do - expect(subject).to eq('min' => 'a', 'max' => 'z', 'exclude_end' => true) + is_expected.to eq('min' => 'a', 'max' => 'z', 'exclude_end' => true) end end @@ -198,7 +198,7 @@ let(:range) { Time.at(0)..Time.at(1) } it 'returns the object hash' do - expect(subject).to eq('min' => Time.at(0), 'max' => Time.at(1)) + is_expected.to eq('min' => Time.at(0), 'max' => Time.at(1)) expect(subject['min'].utc?).to be(true) expect(subject['max'].utc?).to be(true) end @@ -208,7 +208,7 @@ let(:range) { Time.at(0)..Time.at(1) } it 'returns the object hash' do - expect(subject).to eq('min' => Time.at(0).in_time_zone, 'max' => Time.at(1).in_time_zone) + is_expected.to eq('min' => Time.at(0).in_time_zone, 'max' => Time.at(1).in_time_zone) expect(subject['min'].utc?).to be(true) expect(subject['max'].utc?).to be(true) end @@ -218,7 +218,7 @@ let(:range) { Date.new(2020, 1, 1)..Date.new(2020, 1, 2) } it 'returns the object hash' do - expect(subject).to eq('min' => Time.utc(2020, 1, 1), 'max' => Time.utc(2020, 1, 2)) + is_expected.to eq('min' => Time.utc(2020, 1, 1), 'max' => Time.utc(2020, 1, 2)) expect(subject['min'].utc?).to be(true) expect(subject['max'].utc?).to be(true) end @@ -228,7 +228,7 @@ let(:range) { nil } it 'returns nil' do - expect(subject).to be_nil + is_expected.to be_nil end end @@ -236,7 +236,7 @@ let(:range) { { 'min' => 1, 'max' => 5, 'exclude_end' => true } } it 'returns the hash' do - expect(subject).to eq(range) + is_expected.to eq(range) end end @@ -244,7 +244,7 @@ let(:range) { { 'min' => 1 } } it 'returns the hash' do - expect(subject).to eq(range) + is_expected.to eq(range) end end end @@ -256,7 +256,7 @@ let(:range) { '3' } it 'returns a string' do - expect(subject).to eq('3') + is_expected.to eq('3') end end @@ -270,7 +270,7 @@ let(:range) { '3' } it 'returns nil' do - expect(subject).to be_nil + is_expected.to be_nil end end @@ -278,7 +278,7 @@ let(:range) { { 'min' => 1, 'max' => 5, 'exclude_end^' => true } } it 'removes the bogus fields' do - expect(subject).to eq({ 'min' => 1, 'max' => 5 }) + is_expected.to eq({ 'min' => 1, 'max' => 5 }) end end @@ -286,7 +286,7 @@ let(:range) { { 'min^' => 1, 'max^' => 5, 'exclude_end^' => true } } it 'returns nil' do - expect(subject).to be_nil + is_expected.to be_nil end end diff --git a/spec/active_document/extensions/raw_value_spec.rb b/spec/active_document/extensions/raw_value_spec.rb index 87666a3b9..ac4cd38dc 100644 --- a/spec/active_document/extensions/raw_value_spec.rb +++ b/spec/active_document/extensions/raw_value_spec.rb @@ -11,7 +11,7 @@ let(:raw_value) { 'Hello World!' } it 'returns the value' do - expect(subject).to eq 'Hello World!' + is_expected.to eq 'Hello World!' end end @@ -19,7 +19,7 @@ let(:raw_value) { 42 } it 'returns the value' do - expect(subject).to eq 42 + is_expected.to eq 42 end end end @@ -31,7 +31,7 @@ let(:raw_value) { 'Hello World!' } it 'returns the value' do - expect(subject).to eq 'Hello World!' + is_expected.to eq 'Hello World!' end end @@ -39,7 +39,7 @@ let(:raw_value) { 42 } it 'returns the value' do - expect(subject).to eq 42 + is_expected.to eq 42 end end end @@ -51,7 +51,7 @@ let(:raw_value) { 'Hello World!' } it 'returns the inspection' do - expect(subject).to eq 'RawValue: "Hello World!"' + is_expected.to eq 'RawValue: "Hello World!"' end end @@ -59,7 +59,7 @@ let(:raw_value) { 42 } it 'returns the inspection' do - expect(subject).to eq 'RawValue: 42' + is_expected.to eq 'RawValue: 42' end end end diff --git a/spec/active_document/fields/field_types_spec.rb b/spec/active_document/fields/field_types_spec.rb index d752ca47b..76f230c5d 100644 --- a/spec/active_document/fields/field_types_spec.rb +++ b/spec/active_document/fields/field_types_spec.rb @@ -2,9 +2,11 @@ require 'spec_helper' -describe Mongoid::Fields::FieldTypes do +describe ActiveDocument::Fields::FieldTypes do - after 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 @@ -20,7 +22,7 @@ end context 'when value is a default mapped string' do - let(:type) { 'double' } + let(:type) { 'float' } it 'uses the default mapped type' do is_expected.to eq Float @@ -28,7 +30,7 @@ end context 'when value is a custom mapped symbol' do - before { described_class.define('number', Integer) } + before { described_class.define_type('number', Integer) } let(:type) { :number } @@ -38,7 +40,7 @@ end context 'when value is a custom mapped string' do - before { described_class.define(:number, Float) } + before { described_class.define_type(:number, Float) } let(:type) { 'number' } @@ -51,7 +53,7 @@ let(:type) { :my_value } it 'returns nil' do - is_expected.to eq nil + is_expected.to be_nil end end @@ -59,7 +61,7 @@ let(:type) { 'my_value' } it 'returns nil' do - is_expected.to eq nil + is_expected.to be_nil end end @@ -96,16 +98,16 @@ Boolean end - it 'returns Mongoid::Boolean type' do - is_expected.to eq Mongoid::Boolean + it 'returns ActiveDocument::Boolean type' do + is_expected.to eq ActiveDocument::Boolean end end context 'when value is nil' do let(:type) { nil } - it 'returns Object type' do - is_expected.to eq Object + it 'returns nil' do + is_expected.to be_nil end end end @@ -113,17 +115,17 @@ describe '.define' do it 'can define a new type' do - described_class.define(:my_string, String) + 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(:integer, String) + 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(:integer, String) + described_class.define_type(:integer, String) expect(described_class::DEFAULT_MAPPING[:integer]).to eq Integer end end @@ -131,7 +133,7 @@ describe '.delete' do it 'can delete a custom type' do - described_class.define(:my_string, String) + 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 be_nil @@ -147,4 +149,21 @@ 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/active_document/fields_spec.rb b/spec/active_document/fields_spec.rb index 7b1229fc5..641df12d1 100644 --- a/spec/active_document/fields_spec.rb +++ b/spec/active_document/fields_spec.rb @@ -4,7 +4,7 @@ describe ActiveDocument::Fields do - describe "#\{field}_translations" do + describe '{field}_translations' do let(:product) do Product.new @@ -73,7 +73,7 @@ end end - describe "#\{field}_translations=" do + describe '{field}_translations=' do let(:product) do Product.new @@ -331,11 +331,10 @@ bigdecimal: BigDecimal, big_decimal: BigDecimal, binary: BSON::Binary, - boolean: Mongoid::Boolean, + boolean: ActiveDocument::Boolean, date: Date, datetime: DateTime, date_time: DateTime, - decimal128: BSON::Decimal128, double: Float, float: Float, hash: Hash, @@ -346,10 +345,10 @@ regexp: Regexp, set: Set, string: String, - stringified_symbol: Mongoid::StringifiedSymbol, + stringified_symbol: ActiveDocument::StringifiedSymbol, symbol: Symbol, time: Time, - time_with_zone: ActiveSupport::TimeWithZone + undefined: Object }.each do |field_type, field_klass| it "converts Symbol :#{field_type} to #{field_klass}" do @@ -362,18 +361,18 @@ end context 'when using an unknown symbol' do - it 'raises InvalidFieldType' do + it 'raises UnknownFieldType' do expect do - klass.field(:test, type: :bogus) - end.to raise_error(ActiveDocument::Errors::InvalidFieldType, /defines a field :test with an unknown :type value :bogus/) + klass.field(:test, type: :bogus) + end.to raise_error(ActiveDocument::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 expect do - klass.field(:test, type: 'bogus') - end.to raise_error(ActiveDocument::Errors::InvalidFieldType, /defines a field :test with an unknown :type value "bogus"/) + klass.field(:test, type: 'bogus') + end.to raise_error(ActiveDocument::Errors::UnknownFieldType, /declares a field :test with an unknown :type value "bogus"/) end end end @@ -529,49 +528,6 @@ end end end - - context 'when the field is declared as BSON::Decimal128' do - let(:document) { Mop.create!(decimal128_field: BSON::Decimal128.new(Math::PI.to_s)).reload } - - shared_examples 'BSON::Decimal128 is BigDecimal' do - it 'returns a BigDecimal' do - expect(document.decimal128_field).to be_a BigDecimal - end - end - - shared_examples 'BSON::Decimal128 is BSON::Decimal128' do - it 'returns a BSON::Decimal128' do - expect(document.decimal128_field).to be_a BSON::Decimal128 - end - end - - it 'is declared as BSON::Decimal128' do - expect(Mop.fields['decimal128_field'].type).to eq BSON::Decimal128 - end - - context 'when BSON version <= 4' do - max_bson_version '4.99.99' - it_behaves_like 'BSON::Decimal128 is BSON::Decimal128' - end - - context 'when BSON version >= 5' do - min_bson_version '5.0.0' - - context 'when allow_bson5_decimal128 is false' do - config_override :allow_bson5_decimal128, false - it_behaves_like 'BSON::Decimal128 is BigDecimal' - end - - context 'when allow_bson5_decimal128 is true' do - config_override :allow_bson5_decimal128, true - it_behaves_like 'BSON::Decimal128 is BSON::Decimal128' - end - - context 'when allow_bson5_decimal128 is default' do - it_behaves_like 'BSON::Decimal128 is BigDecimal' - end - end - end end describe '#getter_before_type_cast' do @@ -1049,7 +1005,7 @@ expect(Person.field(:testing)).to eq(Person.fields['testing']) end - context "when the field name conflicts with active_document's internals" do + context "when the field name conflicts with ActiveDocument's internals" do %i[_association invalid].each do |meth| context "when the field is named #{meth}" do @@ -1999,56 +1955,9 @@ class DiscriminatorChild2 < DiscriminatorParent end end - describe '.configure DSL' do - - describe '.type method' do - after do - klass = Mongoid::Fields::FieldTypes - klass.instance_variable_set(:@mapping, klass::DEFAULT_MAPPING.dup) - end - - it 'can define a custom type' do - described_class.configure do - type :my_type, Integer - end - - expect(described_class::FieldTypes.get(:my_type)).to eq Integer - end - - it 'can override and existing type' do - described_class.configure do - type :integer, String - end - - expect(described_class::FieldTypes.get(:integer)).to eq String - end - end - - describe '.option method' do - after do - described_class.instance_variable_set(:@options, {}) - end - - it 'can define a custom field option' do - described_class.configure do - 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 be false - expect(instance.errors.full_messages).to eq ["My field can't be blank"] - end + describe '::TYPE_MAPPINGS' do + it 'returns the default mapping' do + expect(described_class::TYPE_MAPPINGS).to eq ActiveDocument::Fields::FieldTypes::DEFAULT_MAPPING end end diff --git a/spec/active_document/matcher/expression_spec.rb b/spec/active_document/matcher/expression_spec.rb index e19c06cba..268d97e3a 100644 --- a/spec/active_document/matcher/expression_spec.rb +++ b/spec/active_document/matcher/expression_spec.rb @@ -24,7 +24,7 @@ let(:expr) { { title: 'Sir', '$comment' => 'hello' } } it 'ignores the $comment' do - expect(subject).to be true + is_expected.to be true end end end diff --git a/spec/integration/persistence/range_field_spec.rb b/spec/integration/persistence/range_field_spec.rb index 03d10a0b0..765780b5d 100644 --- a/spec/integration/persistence/range_field_spec.rb +++ b/spec/integration/persistence/range_field_spec.rb @@ -18,69 +18,69 @@ let(:value) { 1..3 } it do - expect(subject).to eq(1..3) + is_expected.to eq(1..3) end end context 'when Integer exclude_end' do let(:value) { 1...3 } - it { expect(subject).to eq(1...3) } + it { is_expected.to eq(1...3) } end context 'when endless' do let(:value) { 3.. } - it { expect(subject).to eq(3..) } + it { is_expected.to eq(3..) } end context 'when endless exclude_end' do let(:value) { 3... } - it { expect(subject).to eq(3...) } + it { is_expected.to eq(3...) } end context 'when beginning-less' do let(:value) { ..3 } - it { expect(subject).to eq(..3) } + it { is_expected.to eq(..3) } end context 'when beginning-less exclude_end' do let(:value) { ...3 } - it { expect(subject).to eq(...3) } + it { is_expected.to eq(...3) } end context 'when Hash' do let(:value) { { 'min' => 1, 'max' => 3 } } - it { expect(subject).to eq(1..3) } + it { is_expected.to eq(1..3) } end context 'when Hash exclude_end' do let(:value) { { 'min' => 1, 'max' => 3, 'exclude_end' => true } } - it { expect(subject).to eq(1...3) } + it { is_expected.to eq(1...3) } end context 'when Hash' do let(:value) { { min: 1, max: 3 } } - it { expect(subject).to eq(1..3) } + it { is_expected.to eq(1..3) } end context 'when Hash exclude_end' do let(:value) { { min: 1, max: 3, exclude_end: true } } - it { expect(subject).to eq(1...3) } + it { is_expected.to eq(1...3) } end context 'when Time' do let(:value) { now_utc..later_utc } it do - expect(subject).to be_a Range + is_expected.to be_a Range expect(subject.exclude_end?).to be false expect(subject.first).to be_within(0.01.seconds).of(now_utc) expect(subject.last).to be_within(0.01.seconds).of(later_utc) @@ -93,7 +93,7 @@ let(:value) { now_utc...later_utc } it do - expect(subject).to be_a Range + is_expected.to be_a Range expect(subject.exclude_end?).to be true expect(subject.first).to be_within(0.01.seconds).of(now_utc) expect(subject.last).to be_within(0.01.seconds).of(later_utc) @@ -106,7 +106,7 @@ let(:value) { { 'min' => now_utc, 'max' => later_utc } } it do - expect(subject).to be_a Range + is_expected.to be_a Range expect(subject.exclude_end?).to be false expect(subject.first).to be_within(0.01.seconds).of(now_utc) expect(subject.last).to be_within(0.01.seconds).of(later_utc) @@ -119,7 +119,7 @@ let(:value) { { 'min' => now_utc, 'max' => later_utc, 'exclude_end' => true } } it do - expect(subject).to be_a Range + is_expected.to be_a Range expect(subject.exclude_end?).to be true expect(subject.first).to be_within(0.01.seconds).of(now_utc) expect(subject.last).to be_within(0.01.seconds).of(later_utc) @@ -132,7 +132,7 @@ let(:value) { now_in_zone..later_in_zone } it do - expect(subject).to be_a Range + is_expected.to be_a Range expect(subject.exclude_end?).to be false expect(subject.first).to be_within(0.01.seconds).of(now_utc) expect(subject.last).to be_within(0.01.seconds).of(later_utc) @@ -145,7 +145,7 @@ let(:value) { now_in_zone...later_in_zone } it do - expect(subject).to be_a Range + is_expected.to be_a Range expect(subject.exclude_end?).to be true expect(subject.first).to be_within(0.01.seconds).of(now_utc) expect(subject.last).to be_within(0.01.seconds).of(later_utc) @@ -158,7 +158,7 @@ let(:value) { { 'min' => now_in_zone, 'max' => later_in_zone } } it do - expect(subject).to be_a Range + is_expected.to be_a Range expect(subject.exclude_end?).to be false expect(subject.first).to be_within(0.01.seconds).of(now_utc) expect(subject.last).to be_within(0.01.seconds).of(later_utc) @@ -171,7 +171,7 @@ let(:value) { { 'min' => now_in_zone, 'max' => later_in_zone, 'exclude_end' => true } } it do - expect(subject).to be_a Range + is_expected.to be_a Range expect(subject.exclude_end?).to be true expect(subject.first).to be_within(0.01.seconds).of(now_utc) expect(subject.last).to be_within(0.01.seconds).of(later_utc) @@ -188,81 +188,81 @@ let(:value) { 1..3 } it do - expect(subject).to eq('max' => 3, 'min' => 1) + is_expected.to eq('max' => 3, 'min' => 1) end end context 'when Integer exclude_end' do let(:value) { 1...3 } - it { expect(subject).to eq('max' => 3, 'min' => 1, 'exclude_end' => true) } + it { is_expected.to eq('max' => 3, 'min' => 1, 'exclude_end' => true) } end context 'when descending' do let(:value) { 3..1 } - it { expect(subject).to eq('max' => 1, 'min' => 3) } + it { is_expected.to eq('max' => 1, 'min' => 3) } end context 'when descending exclude_end' do let(:value) { 3...1 } - it { expect(subject).to eq('max' => 1, 'min' => 3, 'exclude_end' => true) } + it { is_expected.to eq('max' => 1, 'min' => 3, 'exclude_end' => true) } end context 'when endless' do let(:value) { 3.. } - it { expect(subject).to eq('min' => 3) } + it { is_expected.to eq('min' => 3) } end context 'when endless exclude_end' do let(:value) { 3... } - it { expect(subject).to eq('min' => 3, 'exclude_end' => true) } + it { is_expected.to eq('min' => 3, 'exclude_end' => true) } end context 'when beginning-less' do let(:value) { ..3 } - it { expect(subject).to eq('max' => 3) } + it { is_expected.to eq('max' => 3) } end context 'when beginning-less exclude_end' do let(:value) { ...3 } - it { expect(subject).to eq('max' => 3, 'exclude_end' => true) } + it { is_expected.to eq('max' => 3, 'exclude_end' => true) } end context 'when Hash' do let(:value) { { 'min' => 1, 'max' => 3 } } - it { expect(subject).to eq('max' => 3, 'min' => 1) } + it { is_expected.to eq('max' => 3, 'min' => 1) } end context 'when Hash exclude_end' do let(:value) { { 'min' => 1, 'max' => 3, 'exclude_end' => true } } - it { expect(subject).to eq('max' => 3, 'min' => 1, 'exclude_end' => true) } + it { is_expected.to eq('max' => 3, 'min' => 1, 'exclude_end' => true) } end context 'when Hash' do let(:value) { { min: 1, max: 3 } } - it { expect(subject).to eq('max' => 3, 'min' => 1) } + it { is_expected.to eq('max' => 3, 'min' => 1) } end context 'when Hash exclude_end' do let(:value) { { min: 1, max: 3, exclude_end: true } } - it { expect(subject).to eq('max' => 3, 'min' => 1, 'exclude_end' => true) } + it { is_expected.to eq('max' => 3, 'min' => 1, 'exclude_end' => true) } end context 'when Time' do let(:value) { now_utc..later_utc } it do - expect(subject).to be_a Hash + is_expected.to be_a Hash expect(subject['exclude_end']).to be_nil expect(subject['min']).to be_within(0.01.seconds).of(now_utc) expect(subject['max']).to be_within(0.01.seconds).of(later_utc) @@ -275,7 +275,7 @@ let(:value) { now_utc...later_utc } it do - expect(subject).to be_a Hash + is_expected.to be_a Hash expect(subject['exclude_end']).to be true expect(subject['min']).to be_within(0.01.seconds).of(now_utc) expect(subject['max']).to be_within(0.01.seconds).of(later_utc) @@ -288,7 +288,7 @@ let(:value) { { 'min' => now_utc, 'max' => later_utc } } it do - expect(subject).to be_a Hash + is_expected.to be_a Hash expect(subject['exclude_end']).to be_nil expect(subject['min']).to be_within(0.01.seconds).of(now_utc) expect(subject['max']).to be_within(0.01.seconds).of(later_utc) @@ -301,7 +301,7 @@ let(:value) { { 'min' => now_utc, 'max' => later_utc, 'exclude_end' => true } } it do - expect(subject).to be_a Hash + is_expected.to be_a Hash expect(subject['exclude_end']).to be true expect(subject['min']).to be_within(0.01.seconds).of(now_utc) expect(subject['max']).to be_within(0.01.seconds).of(later_utc) @@ -314,7 +314,7 @@ let(:value) { now_in_zone..later_in_zone } it do - expect(subject).to be_a Hash + is_expected.to be_a Hash expect(subject['exclude_end']).to be_nil expect(subject['min']).to be_within(0.01.seconds).of(now_utc) expect(subject['max']).to be_within(0.01.seconds).of(later_utc) @@ -327,7 +327,7 @@ let(:value) { now_in_zone...later_in_zone } it do - expect(subject).to be_a Hash + is_expected.to be_a Hash expect(subject['exclude_end']).to be true expect(subject['min']).to be_within(0.01.seconds).of(now_utc) expect(subject['max']).to be_within(0.01.seconds).of(later_utc) @@ -340,7 +340,7 @@ let(:value) { { 'min' => now_in_zone, 'max' => later_in_zone } } it do - expect(subject).to be_a Hash + is_expected.to be_a Hash expect(subject['exclude_end']).to be_nil expect(subject['min']).to be_within(0.01.seconds).of(now_utc) expect(subject['max']).to be_within(0.01.seconds).of(later_utc) @@ -353,7 +353,7 @@ let(:value) { { 'min' => now_in_zone, 'max' => later_in_zone, 'exclude_end' => true } } it do - expect(subject).to be_a Hash + is_expected.to be_a Hash expect(subject['exclude_end']).to be true expect(subject['min']).to be_within(0.01.seconds).of(now_utc) expect(subject['max']).to be_within(0.01.seconds).of(later_utc)