Skip to content

Commit 6fc3273

Browse files
committed
1 parent c0e78e6 commit 6fc3273

File tree

8 files changed

+369
-112
lines changed

8 files changed

+369
-112
lines changed

docs/reference/fields.txt

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

900+
You may optionally declare a symbol mapping for the new field type in an initializer:
901+
902+
.. code-block:: ruby
903+
904+
# in /config/initializers/mongoid_custom_fields.rb
905+
906+
Mongoid::Fields.configure do
907+
type :point, Point
908+
end
909+
910+
900911
Then make a Ruby class to represent the type. This class must define methods
901912
used for MongoDB serialization and deserialization as follows:
902913

@@ -992,8 +1003,10 @@ specifiying its handler function as a block:
9921003

9931004
# in /config/initializers/mongoid_custom_fields.rb
9941005

995-
Mongoid::Fields.option :required do |model, field, value|
996-
model.validates_presence_of field if value
1006+
Mongoid::Fields.configure do
1007+
option :required do |model, field, value|
1008+
model.validates_presence_of field.name if value
1009+
end
9971010
end
9981011

9991012
Then, use it your model class:

docs/release-notes/mongoid-8.0.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,22 @@ Mongoid 7 behavior:
303303
end
304304

305305

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

lib/config/locales/en.yml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,10 @@ en:
164164
resolution: "When defining the field :%{name} on '%{klass}', please provide
165165
valid options for the field. These are currently: %{valid}. If you
166166
meant to define a custom field option, please do so first as follows:\n\n
167-
\_\_Mongoid::Fields.option :%{option} do |model, field, value|\n
168-
\_\_\_\_# Your logic here...\n
167+
\_\_Mongoid::Fields.configure do\n
168+
\_\_\_\_option :%{option} do |model, field, value|\n
169+
\_\_\_\_\_\_# Your logic here...\n
170+
\_\_\_\_end\n
169171
\_\_end\n
170172
\_\_class %{klass}\n
171173
\_\_\_\_include Mongoid::Document\n
@@ -174,12 +176,21 @@ en:
174176
Refer to:
175177
https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-options"
176178
invalid_field_type:
177-
message: "Invalid field type %{type_inspection} for field '%{field}' on model '%{klass}'."
178-
summary: "Model '%{klass}' defines a field '%{field}' with an unknown type value
179-
%{type_inspection}."
180-
resolution: "Please provide a valid type value for the field.
179+
message: "Invalid field type %{type_inspection} for field :%{field} on model '%{klass}'."
180+
summary: "Model '%{klass}' defines a field :%{field} with an unknown :type value
181+
%{type_inspection}. This value is neither present in Mongoid's default type mapping,
182+
nor defined in a custom field type mapping."
183+
resolution: "Please provide a valid :type value for the field. If you
184+
meant to define a custom field type, please do so first as follows:\n\n
185+
\_\_Mongoid::Fields.configure do\n
186+
\_\_\_\_type %{type_inspection}, YourTypeClass
187+
\_\_end\n
188+
\_\_class %{klass}\n
189+
\_\_\_\_include Mongoid::Document\n
190+
\_\_\_\_field :%{field}, type: %{type_inspection}\n
191+
\_\_end\n\n
181192
Refer to:
182-
https://docs.mongodb.com/mongoid/current/reference/fields/#using-symbols-or-strings-instead-of-classes"
193+
https://docs.mongodb.com/mongoid/current/reference/fields/#custom-field-types"
183194
invalid_includes:
184195
message: "Invalid includes directive: %{klass}.includes(%{args})"
185196
summary: "Eager loading in Mongoid only supports providing arguments

lib/mongoid/fields.rb

Lines changed: 31 additions & 34 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,27 +15,6 @@ 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
37-
3818
# Constant for all names of the _id field in a document.
3919
#
4020
# This does not include aliases of _id field.
@@ -243,6 +223,27 @@ def using_object_ids?
243223

244224
class << self
245225

226+
# DSL method used for configuration readability, typically in
227+
# an initializer.
228+
#
229+
# @example
230+
# Mongoid::Fields.configure do
231+
# # do configuration
232+
# end
233+
def configure(&block)
234+
instance_exec(&block)
235+
end
236+
237+
# Defines a field type mapping, for later use in field :type option.
238+
#
239+
# @example
240+
# Mongoid::Fields.configure do
241+
# type :point, Point
242+
# end
243+
def type(symbol, klass)
244+
Fields::FieldTypes.define(symbol, klass)
245+
end
246+
246247
# Stores the provided block to be run when the option name specified is
247248
# defined on a field.
248249
#
@@ -251,8 +252,10 @@ class << self
251252
# provided in the field definition -- even if it is false or nil.
252253
#
253254
# @example
254-
# Mongoid::Fields.option :required do |model, field, value|
255-
# model.validates_presence_of field if value
255+
# Mongoid::Fields.configure do
256+
# option :required do |model, field, value|
257+
# model.validates_presence_of field.name if value
258+
# end
256259
# end
257260
#
258261
# @param [ Symbol ] option_name the option name to match against
@@ -729,22 +732,16 @@ def remove_defaults(name)
729732

730733
def field_for(name, options)
731734
opts = options.merge(klass: self)
732-
type_mapping = TYPE_MAPPINGS[options[:type]]
733-
opts[:type] = type_mapping || unmapped_type(options)
734-
unless opts[:type].is_a?(Class)
735-
raise Errors::InvalidFieldType.new(self, name, options[:type])
736-
end
735+
opts[:type] = field_type_klass_for(name, options[:type])
737736
return Fields::Localized.new(name, opts) if options[:localize]
738737
return Fields::ForeignKey.new(name, opts) if options[:identity]
739738
Fields::Standard.new(name, opts)
740739
end
741740

742-
def unmapped_type(options)
743-
if "Boolean" == options[:type].to_s
744-
Mongoid::Boolean
745-
else
746-
options[:type] || Object
747-
end
741+
def field_type_klass_for(field, type)
742+
klass = Fields::FieldTypes.get(type)
743+
return klass if klass
744+
raise Mongoid::Errors::InvalidFieldType.new(self.name, field, type)
748745
end
749746
end
750747
end

lib/mongoid/fields/field_types.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
module Fields
5+
6+
# Singleton module which contains a cache for field type definitions.
7+
# Custom field types can be configured.
8+
module FieldTypes
9+
extend self
10+
11+
# For fields defined with symbols use the correct class.
12+
DEFAULT_MAPPING = {
13+
array: Array,
14+
bigdecimal: BigDecimal,
15+
big_decimal: BigDecimal,
16+
binary: BSON::Binary,
17+
boolean: Mongoid::Boolean,
18+
date: Date,
19+
datetime: DateTime,
20+
date_time: DateTime,
21+
decimal128: BSON::Decimal128,
22+
double: Float,
23+
float: Float,
24+
hash: Hash,
25+
integer: Integer,
26+
object: Object,
27+
object_id: BSON::ObjectId,
28+
range: Range,
29+
regexp: Regexp,
30+
set: Set,
31+
string: String,
32+
stringified_symbol: Mongoid::StringifiedSymbol,
33+
symbol: Symbol,
34+
time: Time,
35+
time_with_zone: ActiveSupport::TimeWithZone
36+
}.with_indifferent_access.freeze
37+
38+
def get(value)
39+
value = value.to_sym if value.is_a?(String)
40+
mapping[value] || handle_unmapped_type(value)
41+
end
42+
43+
def define(symbol, klass)
44+
mapping[symbol.to_sym] = klass
45+
end
46+
47+
delegate :delete, to: :mapping
48+
49+
private
50+
51+
def mapping
52+
@mapping ||= DEFAULT_MAPPING.dup
53+
end
54+
55+
def handle_unmapped_type(type)
56+
return Object if type.nil?
57+
58+
if type.is_a?(Module)
59+
return Mongoid::Boolean if type.to_s == 'Boolean'
60+
return type
61+
end
62+
63+
nil
64+
end
65+
end
66+
end
67+
end

spec/mongoid/errors/invalid_field_type_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717

1818
it "contains the problem in the message" do
1919
expect(error.message).to include(
20-
"Invalid field type :stringgy for field 'first_name' on model 'Person'."
20+
"Invalid field type :stringgy for field :first_name on model 'Person'."
2121
)
2222
end
2323

2424
it "contains the summary in the message" do
2525
expect(error.message).to include(
26-
"Model 'Person' defines a field 'first_name' with an unknown type value :stringgy."
26+
"Model 'Person' defines a field :first_name with an unknown :type value :stringgy."
2727
)
2828
end
2929
end
@@ -35,20 +35,20 @@
3535

3636
it "contains the problem in the message" do
3737
expect(error.message).to include(
38-
%q,Invalid field type "stringgy" for field 'first_name' on model 'Person'.,
38+
%q,Invalid field type "stringgy" for field :first_name on model 'Person'.,
3939
)
4040
end
4141

4242
it "contains the summary in the message" do
4343
expect(error.message).to include(
44-
%q,Model 'Person' defines a field 'first_name' with an unknown type value "stringgy".,
44+
%q,Model 'Person' defines a field :first_name with an unknown :type value "stringgy".,
4545
)
4646
end
4747
end
4848

4949
it "contains the resolution in the message" do
5050
expect(error.message).to include(
51-
'Please provide a valid type value for the field.'
51+
'Please provide a valid :type value for the field.'
5252
)
5353
end
5454
end

0 commit comments

Comments
 (0)