Skip to content

Commit f28fd74

Browse files
committed
MONGOID-5336 User-defined symbol field types - squashed commits
1 parent 449022f commit f28fd74

File tree

12 files changed

+542
-129
lines changed

12 files changed

+542
-129
lines changed

docs/reference/fields.txt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,17 @@ can use in our model class as follows:
913913
field :location, type: Point
914914
end
915915

916+
You may optionally declare a mapping for the new field type in an initializer:
917+
918+
.. code-block:: ruby
919+
920+
# in /config/initializers/mongoid_custom_fields.rb
921+
922+
Mongoid::Fields.configure do
923+
define_type :point, Point
924+
end
925+
926+
916927
Then make a Ruby class to represent the type. This class must define methods
917928
used for MongoDB serialization and deserialization as follows:
918929

@@ -1008,8 +1019,10 @@ specifiying its handler function as a block:
10081019

10091020
# in /config/initializers/mongoid_custom_fields.rb
10101021

1011-
Mongoid::Fields.option :required do |model, field, value|
1012-
model.validates_presence_of field if value
1022+
Mongoid::Fields.configure do
1023+
option :required do |model, field, value|
1024+
model.validates_presence_of field.name if value
1025+
end
10131026
end
10141027

10151028
Then, use it your model class:

docs/release-notes/mongoid-8.0.txt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ Mongoid 8 behavior:
289289
include Mongoid::Document
290290

291291
field :name, type: :bogus
292-
# => raises Mongoid::Errors::InvalidFieldType
292+
# => raises Mongoid::Errors::UnknownFieldType
293293
end
294294

295295
Mongoid 7 behavior:
@@ -305,6 +305,22 @@ Mongoid 7 behavior:
305305
end
306306

307307

308+
Support for Defining Custom Field Type Values
309+
---------------------------------------------
310+
311+
Mongoid 8.0 adds the ability to define custom ``field :type`` Symbol values as follows:
312+
313+
.. code-block:: ruby
314+
315+
# in /config/initializers/mongoid_custom_fields.rb
316+
317+
Mongoid::Fields.configure do
318+
define_type :point, Point
319+
end
320+
321+
Refer to the :ref:`docs <http://docs.mongodb.org/manual/reference/fields/#custom-field-types>` for details.
322+
323+
308324
Removed ``:drop_dups`` Option from Indexes
309325
------------------------------------------
310326

lib/config/locales/en.yml

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -189,22 +189,25 @@ en:
189189
resolution: "When defining the field :%{name} on '%{klass}', please provide
190190
valid options for the field. These are currently: %{valid}. If you
191191
meant to define a custom field option, please do so first as follows:\n\n
192-
\_\_Mongoid::Fields.option :%{option} do |model, field, value|\n
193-
\_\_\_\_# Your logic here...\n
192+
\_\_Mongoid::Fields.configure do\n
193+
\_\_\_\_option :%{option} do |model, field, value|\n
194+
\_\_\_\_\_\_# Your logic here...\n
195+
\_\_\_\_end\n
194196
\_\_end\n
195197
\_\_class %{klass}\n
196198
\_\_\_\_include Mongoid::Document\n
197199
\_\_\_\_field :%{name}, %{option}: true\n
198200
\_\_end\n\n
199201
Refer to:
200202
https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-options"
201-
invalid_field_type:
202-
message: "Invalid field type %{type_inspection} for field '%{field}' on model '%{klass}'."
203-
summary: "Model '%{klass}' defines a field '%{field}' with an unknown type value
204-
%{type_inspection}."
205-
resolution: "Please provide a valid type value for the field.
203+
invalid_field_type_definition:
204+
message: "The field type definition of %{type_inspection} to %{klass_inspection} is invalid."
205+
summary: "In the field type definition, either field_type %{type_inspection} is not
206+
a Symbol or String, and/or klass %{klass_inspection} is not a Class or Module."
207+
resolution: "Please ensure you are specifying field_type as either a Symbol or String,
208+
and klass as a Class or Module.\n\n
206209
Refer to:
207-
https://docs.mongodb.com/mongoid/current/reference/fields/#using-symbols-or-strings-instead-of-classes"
210+
https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-types"
208211
invalid_includes:
209212
message: "Invalid includes directive: %{klass}.includes(%{args})"
210213
summary: "Eager loading in Mongoid only supports providing arguments
@@ -584,6 +587,22 @@ en:
584587
resolution: "Define the field '%{name}' in %{klass}, or include
585588
Mongoid::Attributes::Dynamic in %{klass} if you intend to
586589
store values in fields that are not explicitly defined."
590+
unknown_field_type:
591+
message: "Unknown field type %{type_inspection} for field '%{field}' on model '%{klass}'."
592+
summary: "Model '%{klass}' declares a field '%{field}' with an unknown type value
593+
%{type_inspection}. This value is neither present in Mongoid's default type mapping,
594+
nor defined in a custom field type mapping."
595+
resolution: "Please provide a known type value for the field. If you
596+
meant to define a custom field type, please do so first as follows:\n\n
597+
\_\_Mongoid::Fields.configure do\n
598+
\_\_\_\_define_type %{type_inspection}, YourTypeClass
599+
\_\_end\n
600+
\_\_class %{klass}\n
601+
\_\_\_\_include Mongoid::Document\n
602+
\_\_\_\_field :%{field}, type: %{type_inspection}\n
603+
\_\_end\n\n
604+
Refer to:
605+
https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-types"
587606
unknown_model:
588607
message: "Attempted to instantiate an object of the unknown model '%{klass}'."
589608
summary: "A document with the value '%{value}' at the key '_type' was used to

lib/mongoid/errors.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
require "mongoid/errors/invalid_dependent_strategy"
1515
require "mongoid/errors/invalid_field"
1616
require "mongoid/errors/invalid_field_option"
17-
require "mongoid/errors/invalid_field_type"
17+
require "mongoid/errors/invalid_field_type_definition"
1818
require "mongoid/errors/invalid_find"
1919
require "mongoid/errors/invalid_includes"
2020
require "mongoid/errors/invalid_index"
@@ -56,6 +56,7 @@
5656
require "mongoid/errors/scope_overwrite"
5757
require "mongoid/errors/too_many_nested_attribute_records"
5858
require "mongoid/errors/unknown_attribute"
59+
require "mongoid/errors/unknown_field_type"
5960
require "mongoid/errors/unknown_model"
6061
require "mongoid/errors/unsaved_document"
6162
require "mongoid/errors/unsupported_javascript"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
module Errors
5+
6+
# This error is raised when trying to define a field type mapping with
7+
# invalid argument types.
8+
class InvalidFieldTypeDefinition < MongoidError
9+
10+
# Create the new error.
11+
#
12+
# @example Instantiate the error.
13+
# InvalidFieldTypeDefinition.new('number', 123)
14+
#
15+
# @param [ Object ] field_type The object which is expected to a be Symbol or String.
16+
# @param [ Object ] klass The object which is expected to be a Class or Module.
17+
def initialize(field_type, klass)
18+
type_inspection = field_type.try(:inspect) || field_type.class.inspect
19+
klass_inspection = klass.try(:inspect) || klass.class.inspect
20+
super(
21+
compose_message('invalid_field_type_definition',
22+
type_inspection: type_inspection, klass_inspection: klass_inspection)
23+
)
24+
end
25+
end
26+
end
27+
end

lib/mongoid/errors/invalid_field_type.rb renamed to lib/mongoid/errors/unknown_field_type.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@ module Errors
55

66
# This error is raised when trying to define a field using a :type option value
77
# that is not present in the field type mapping.
8-
class InvalidFieldType < MongoidError
8+
class UnknownFieldType < MongoidError
99

1010
# Create the new error.
1111
#
1212
# @example Instantiate the error.
13-
# InvalidFieldType.new('Person', 'first_name', 'stringgy')
13+
# UnknownFieldType.new('Person', 'first_name', 'stringgy')
1414
#
1515
# @param [ String ] klass The model class.
1616
# @param [ String ] field The field on which the invalid type is used.
1717
# @param [ Symbol | String ] type The value of the field :type option.
1818
def initialize(klass, field, type)
1919
super(
20-
compose_message('invalid_field_type',
20+
compose_message('unknown_field_type',
2121
klass: klass, field: field, type_inspection: type.inspect)
2222
)
2323
end

lib/mongoid/fields.rb

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require "mongoid/fields/foreign_key"
55
require "mongoid/fields/localized"
66
require "mongoid/fields/validators"
7+
require "mongoid/fields/field_types"
78

89
module Mongoid
910

@@ -14,26 +15,8 @@ module Fields
1415
StringifiedSymbol = Mongoid::StringifiedSymbol
1516
Boolean = Mongoid::Boolean
1617

17-
# For fields defined with symbols use the correct class.
18-
TYPE_MAPPINGS = {
19-
array: Array,
20-
big_decimal: BigDecimal,
21-
binary: BSON::Binary,
22-
boolean: Mongoid::Boolean,
23-
date: Date,
24-
date_time: DateTime,
25-
float: Float,
26-
hash: Hash,
27-
integer: Integer,
28-
object_id: BSON::ObjectId,
29-
range: Range,
30-
regexp: Regexp,
31-
set: Set,
32-
string: String,
33-
stringified_symbol: StringifiedSymbol,
34-
symbol: Symbol,
35-
time: Time
36-
}.with_indifferent_access
18+
# @deprecated
19+
TYPE_MAPPINGS = ::Mongoid::Fields::FieldTypes::DEFAULT_MAPPING
3720

3821
# Constant for all names of the _id field in a document.
3922
#
@@ -45,7 +28,7 @@ module Fields
4528
# BSON classes that are not supported as field types
4629
#
4730
# @api private
48-
INVALID_BSON_CLASSES = [ BSON::Decimal128, BSON::Int32, BSON::Int64 ].freeze
31+
UNSUPPORTED_BSON_TYPES = [ BSON::Decimal128, BSON::Int32, BSON::Int64 ].freeze
4932

5033
module ClassMethods
5134
# Returns the list of id fields for this model class, as both strings
@@ -274,6 +257,33 @@ def validate_writable_field_name!(name)
274257

275258
class << self
276259

260+
# DSL method used for configuration readability, typically in
261+
# an initializer.
262+
#
263+
# @example
264+
# Mongoid::Fields.configure do
265+
# # do configuration
266+
# end
267+
def configure(&block)
268+
instance_exec(&block)
269+
end
270+
271+
# Defines a field type mapping, for later use in field :type option.
272+
#
273+
# @example
274+
# Mongoid::Fields.configure do
275+
# define_type :point, Point
276+
# end
277+
#
278+
# @param [ Symbol | String ] field_type the identifier of the
279+
# defined type. This identifier will be accessible as either a
280+
# string or a symbol regardless of the type passed to this method.
281+
# @param [ Module ] klass the class of the defined type, which must
282+
# include mongoize, demongoize, and evolve methods.
283+
def define_type(field_type, klass)
284+
Fields::FieldTypes.define_type(field_type, klass)
285+
end
286+
277287
# Stores the provided block to be run when the option name specified is
278288
# defined on a field.
279289
#
@@ -282,8 +292,10 @@ class << self
282292
# provided in the field definition -- even if it is false or nil.
283293
#
284294
# @example
285-
# Mongoid::Fields.option :required do |model, field, value|
286-
# model.validates_presence_of field if value
295+
# Mongoid::Fields.configure do
296+
# option :required do |model, field, value|
297+
# model.validates_presence_of field.name if value
298+
# end
287299
# end
288300
#
289301
# @param [ Symbol ] option_name the option name to match against
@@ -767,32 +779,28 @@ def remove_defaults(name)
767779

768780
def field_for(name, options)
769781
opts = options.merge(klass: self)
770-
type_mapping = TYPE_MAPPINGS[options[:type]]
771-
opts[:type] = type_mapping || unmapped_type(options)
772-
if !opts[:type].is_a?(Class)
773-
raise Errors::InvalidFieldType.new(self, name, options[:type])
774-
else
775-
if INVALID_BSON_CLASSES.include?(opts[:type])
776-
warn_message = "Using #{opts[:type]} as the field type is not supported. "
777-
if opts[:type] == BSON::Decimal128
778-
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+."
779-
else
780-
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."
781-
end
782-
Mongoid.logger.warn(warn_message)
782+
if type = options[:type]
783+
type = Fields::FieldTypes.get(type)
784+
unless type
785+
raise Mongoid::Errors::UnknownFieldType.new(self.name, name, type)
783786
end
787+
opts[:type] = type
788+
warn_unsupported_bson_type(type)
784789
end
785790
return Fields::Localized.new(name, opts) if options[:localize]
786791
return Fields::ForeignKey.new(name, opts) if options[:identity]
787792
Fields::Standard.new(name, opts)
788793
end
789794

790-
def unmapped_type(options)
791-
if "Boolean" == options[:type].to_s
792-
Mongoid::Boolean
795+
def warn_unsupported_bson_type(type)
796+
return unless UNSUPPORTED_BSON_TYPES.include?(type)
797+
warn_message = "Using #{type} as the field type is not supported. "
798+
if type == BSON::Decimal128
799+
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+."
793800
else
794-
options[:type] || Object
801+
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."
795802
end
803+
Mongoid.logger.warn(warn_message)
796804
end
797805
end
798806
end

0 commit comments

Comments
 (0)