mongoid 9.0.1 → 9.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/config/locales/en.yml +16 -0
- data/lib/mongoid/association/accessors.rb +7 -2
- data/lib/mongoid/association/nested/one.rb +14 -1
- data/lib/mongoid/association/referenced/belongs_to/binding.rb +7 -1
- data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
- data/lib/mongoid/association/referenced/belongs_to.rb +15 -0
- data/lib/mongoid/association/referenced/has_many.rb +9 -8
- data/lib/mongoid/association/referenced/has_one/buildable.rb +3 -8
- data/lib/mongoid/association/referenced/with_polymorphic_criteria.rb +41 -0
- data/lib/mongoid/attributes/nested.rb +2 -1
- data/lib/mongoid/clients/options.rb +14 -1
- data/lib/mongoid/clients/sessions.rb +13 -15
- data/lib/mongoid/composable.rb +2 -0
- data/lib/mongoid/document.rb +2 -0
- data/lib/mongoid/errors/unrecognized_model_alias.rb +53 -0
- data/lib/mongoid/errors/unrecognized_resolver.rb +27 -0
- data/lib/mongoid/errors/unregistered_class.rb +47 -0
- data/lib/mongoid/errors.rb +3 -0
- data/lib/mongoid/identifiable.rb +28 -0
- data/lib/mongoid/matcher.rb +15 -1
- data/lib/mongoid/model_resolver.rb +154 -0
- data/lib/mongoid/persistence_context.rb +15 -9
- data/lib/mongoid/railties/controller_runtime.rb +2 -2
- data/lib/mongoid/serializable.rb +7 -7
- data/lib/mongoid/threaded.rb +96 -28
- data/lib/mongoid/timestamps/timeless.rb +4 -1
- data/lib/mongoid/touchable.rb +1 -1
- data/lib/mongoid/traversable.rb +11 -2
- data/lib/mongoid/validatable/associated.rb +5 -2
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/active_job_spec.rb +24 -20
- data/spec/integration/app_spec.rb +9 -1
- data/spec/integration/associations/belongs_to_spec.rb +129 -0
- data/spec/integration/persistence/collection_options_spec.rb +36 -0
- data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +4 -0
- data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +5 -0
- data/spec/mongoid/association/referenced/belongs_to_spec.rb +58 -21
- data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +4 -0
- data/spec/mongoid/attributes/nested_spec.rb +1 -0
- data/spec/mongoid/clients/options_spec.rb +127 -2
- data/spec/mongoid/clients/transactions_spec.rb +2 -2
- data/spec/mongoid/interceptable_spec.rb +12 -0
- data/spec/mongoid/interceptable_spec_models.rb +12 -0
- data/spec/mongoid/model_resolver_spec.rb +167 -0
- data/spec/mongoid/monkey_patches_spec.rb +1 -1
- data/spec/mongoid/persistence_context_spec.rb +48 -4
- data/spec/mongoid/railties/bson_object_id_serializer_spec.rb +18 -12
- data/spec/mongoid/serializable_spec.rb +16 -9
- data/spec/mongoid/threaded_spec.rb +24 -5
- data/spec/mongoid/validatable/associated_spec.rb +14 -4
- data/spec/rails/controller_extension/controller_runtime_spec.rb +14 -14
- metadata +14 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 421d53636b0e90abcfa52bf637229588d434ff73f14574b1d316b72009f7236b
|
4
|
+
data.tar.gz: fafa07a7cc9b17de0abbb140ca3d021e3041bfe00b67a424c81f6c15018e607d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4f2011d1f85178a8693d653518f06e7ad55c1363958e3577f80d040e3986b595bdcaade0e71696ff762e008c195ee23b4579458708bb9e916f7ddfa8e3b9352
|
7
|
+
data.tar.gz: 8b981e1bc4e904085ecedfe459d31c91d06ed353ba0ceef3d67865ea195f31a36539284246b54537fc5af8b54eafcf1a60415eb88c4850d7cdf3af378d449206
|
data/lib/config/locales/en.yml
CHANGED
@@ -680,6 +680,22 @@ en:
|
|
680
680
|
resolution: "The _type field is a reserved one used by Mongoid to determine the
|
681
681
|
class for instantiating an object. Please don't save data in this field or ensure
|
682
682
|
that any values in this field correspond to valid models."
|
683
|
+
unrecognized_model_alias:
|
684
|
+
message: "Cannot find any model with type %{model_alias}"
|
685
|
+
summary: "A document is trying to load a polymorphic association, but the data refers to a type of object that can't be resolved (%{model_alias}). It might be that you've renamed the target class."
|
686
|
+
resolution: "Register the old name as an alias on the refactored target object, using `identify_as`. This will allow Mongoid to find the target type even if the name no longer matches what was stored in the database."
|
687
|
+
unrecognized_resolver:
|
688
|
+
message: "The model resolver %{resolver} was referenced, but never registered."
|
689
|
+
summary: "A polymorphic association has been configured to use a resolver
|
690
|
+
named %{resolver}, but that resolver has not yet been registered. This
|
691
|
+
might be a typo. Currently registered resolvers are: %{resolvers}."
|
692
|
+
resolution: "Register custom resolvers with
|
693
|
+
`Mongoid::ModelResolver.register_resolver` before attempting to query
|
694
|
+
a polymorphic association."
|
695
|
+
unregistered_class:
|
696
|
+
message: "The class %{klass} is not registered with the resolver %{resolver}."
|
697
|
+
summary: "A polymorphic association using the resolver %{resolver} has tried to link to a model of type %{klass}, but the resolver has no knowledge of any such model. This can happen if the association is configured to use a different resolver than the target mode."
|
698
|
+
resolution: "Make sure the target model is registered with the same resolver as the polymorphic association, using `identify_as`."
|
683
699
|
unsaved_document:
|
684
700
|
message: "Attempted to save %{document} before the parent %{base}."
|
685
701
|
summary: "You cannot call create or create! through the
|
@@ -42,7 +42,8 @@ module Mongoid
|
|
42
42
|
#
|
43
43
|
# @return [ Proxy ] The association.
|
44
44
|
def create_relation(object, association, selected_fields = nil)
|
45
|
-
|
45
|
+
key = @attributes[association.inverse_type]
|
46
|
+
type = key ? association.resolver.model_for(key) : nil
|
46
47
|
target = if t = association.build(self, object, type, selected_fields)
|
47
48
|
association.create_relation(self, t)
|
48
49
|
else
|
@@ -116,7 +117,11 @@ module Mongoid
|
|
116
117
|
# during binding or when cascading callbacks. Whenever we retrieve
|
117
118
|
# associations within the codebase, we use without_autobuild.
|
118
119
|
if !without_autobuild? && association.embedded? && attribute_missing?(field_name)
|
119
|
-
|
120
|
+
# We always allow accessing the parent document of an embedded one.
|
121
|
+
try_get_parent = association.is_a?(
|
122
|
+
Mongoid::Association::Embedded::EmbeddedIn
|
123
|
+
) && field_name == association.key
|
124
|
+
raise Mongoid::Errors::AttributeNotLoaded.new(self.class, field_name) unless try_get_parent
|
120
125
|
end
|
121
126
|
|
122
127
|
if !reload && (value = ivar(name)) != false
|
@@ -53,12 +53,25 @@ module Mongoid
|
|
53
53
|
@attributes = attributes.with_indifferent_access
|
54
54
|
@association = association
|
55
55
|
@options = options
|
56
|
-
@class_name = options[:class_name]
|
56
|
+
@class_name = class_from(options[:class_name])
|
57
57
|
@destroy = @attributes.delete(:_destroy)
|
58
58
|
end
|
59
59
|
|
60
60
|
private
|
61
61
|
|
62
|
+
# Coerces the argument into a class, or defaults to the association's class.
|
63
|
+
#
|
64
|
+
# @param [ String | Mongoid::Document | nil ] name_or_class the value to coerce
|
65
|
+
#
|
66
|
+
# @return [ Mongoid::Document ] the resulting class
|
67
|
+
def class_from(name_or_class)
|
68
|
+
case name_or_class
|
69
|
+
when nil, false then association.klass
|
70
|
+
when String then name_or_class.constantize
|
71
|
+
else name_or_class
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
62
75
|
# Extracts and converts the id to the expected type.
|
63
76
|
#
|
64
77
|
# @return [ BSON::ObjectId | String | Object | nil ] The converted id,
|
@@ -23,7 +23,13 @@ module Mongoid
|
|
23
23
|
binding do
|
24
24
|
check_polymorphic_inverses!(_target)
|
25
25
|
bind_foreign_key(_base, record_id(_target))
|
26
|
-
|
26
|
+
|
27
|
+
# set the inverse type (e.g. "#{name}_type") for new polymorphic associations
|
28
|
+
if _association.inverse_type && !_base.frozen?
|
29
|
+
key = _association.resolver.default_key_for(_target)
|
30
|
+
bind_polymorphic_inverse_type(_base, key)
|
31
|
+
end
|
32
|
+
|
27
33
|
if inverse = _association.inverse(_target)
|
28
34
|
if set_base_association
|
29
35
|
if _base.referenced_many?
|
@@ -33,7 +33,7 @@ module Mongoid
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def query_criteria(object, type)
|
36
|
-
cls = type ? type.constantize : relation_class
|
36
|
+
cls = type ? (type.is_a?(String) ? type.constantize : type) : relation_class
|
37
37
|
crit = cls.criteria
|
38
38
|
crit = crit.apply_scope(scope)
|
39
39
|
crit.where(primary_key => object)
|
@@ -103,6 +103,21 @@ module Mongoid
|
|
103
103
|
@polymorphic ||= !!@options[:polymorphic]
|
104
104
|
end
|
105
105
|
|
106
|
+
# Returns the object responsible for converting polymorphic type references into
|
107
|
+
# class objects, and vice versa. This is obtained via the `:polymorphic` option
|
108
|
+
# that was given when the association was defined.
|
109
|
+
#
|
110
|
+
# See Mongoid::ModelResolver.resolver for how the `:polymorphic` option is
|
111
|
+
# interpreted here.
|
112
|
+
#
|
113
|
+
# @raise KeyError if no such resolver has been registered under the given
|
114
|
+
# identifier.
|
115
|
+
#
|
116
|
+
# @return [ nil | Mongoid::ModelResolver ] the resolver to use
|
117
|
+
def resolver
|
118
|
+
@resolver ||= Mongoid::ModelResolver.resolver(@options[:polymorphic])
|
119
|
+
end
|
120
|
+
|
106
121
|
# The name of the field used to store the type of polymorphic association.
|
107
122
|
#
|
108
123
|
# @return [ String ] The field used to store the type of polymorphic association.
|
@@ -6,6 +6,7 @@ require 'mongoid/association/referenced/has_many/buildable'
|
|
6
6
|
require 'mongoid/association/referenced/has_many/proxy'
|
7
7
|
require 'mongoid/association/referenced/has_many/enumerable'
|
8
8
|
require 'mongoid/association/referenced/has_many/eager'
|
9
|
+
require 'mongoid/association/referenced/with_polymorphic_criteria'
|
9
10
|
|
10
11
|
module Mongoid
|
11
12
|
module Association
|
@@ -15,6 +16,7 @@ module Mongoid
|
|
15
16
|
class HasMany
|
16
17
|
include Relatable
|
17
18
|
include Buildable
|
19
|
+
include WithPolymorphicCriteria
|
18
20
|
|
19
21
|
# The options available for this type of association, in addition to the
|
20
22
|
# common ones.
|
@@ -131,6 +133,12 @@ module Mongoid
|
|
131
133
|
# @param [ Class ] object_class The object class.
|
132
134
|
#
|
133
135
|
# @return [ Mongoid::Criteria ] The criteria object.
|
136
|
+
#
|
137
|
+
# @deprecated in 9.0.x
|
138
|
+
#
|
139
|
+
# It appears as if this method is an artifact left over from a refactoring that renamed it
|
140
|
+
# `with_polymorphic_criterion`, and made it private. Regardless, this method isn't referenced
|
141
|
+
# anywhere else, and is unlikely to be useful to external clients. We should remove it.
|
134
142
|
def add_polymorphic_criterion(criteria, object_class)
|
135
143
|
if polymorphic?
|
136
144
|
criteria.where(type => object_class.name)
|
@@ -138,6 +146,7 @@ module Mongoid
|
|
138
146
|
criteria
|
139
147
|
end
|
140
148
|
end
|
149
|
+
Mongoid.deprecate(self, :add_polymorphic_criterion)
|
141
150
|
|
142
151
|
# Is this association polymorphic?
|
143
152
|
#
|
@@ -222,14 +231,6 @@ module Mongoid
|
|
222
231
|
with_ordering(crit)
|
223
232
|
end
|
224
233
|
|
225
|
-
def with_polymorphic_criterion(criteria, base)
|
226
|
-
if polymorphic?
|
227
|
-
criteria.where(type => base.class.name)
|
228
|
-
else
|
229
|
-
criteria
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
234
|
def with_ordering(criteria)
|
234
235
|
if order
|
235
236
|
criteria.order_by(order)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# rubocop:todo all
|
3
3
|
|
4
|
+
require 'mongoid/association/referenced/with_polymorphic_criteria'
|
5
|
+
|
4
6
|
module Mongoid
|
5
7
|
module Association
|
6
8
|
module Referenced
|
@@ -8,6 +10,7 @@ module Mongoid
|
|
8
10
|
|
9
11
|
# The Builder behavior for has_one associations.
|
10
12
|
module Buildable
|
13
|
+
include WithPolymorphicCriteria
|
11
14
|
|
12
15
|
# This method either takes an _id or an object and queries for the
|
13
16
|
# inverse side using the id or sets the object after clearing the
|
@@ -57,14 +60,6 @@ module Mongoid
|
|
57
60
|
query_criteria(object, base).take
|
58
61
|
end
|
59
62
|
|
60
|
-
def with_polymorphic_criterion(criteria, base)
|
61
|
-
if polymorphic?
|
62
|
-
criteria.where(type => base.class.name)
|
63
|
-
else
|
64
|
-
criteria
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
63
|
def query?(object)
|
69
64
|
object && !object.is_a?(Mongoid::Document)
|
70
65
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Association
|
5
|
+
module Referenced
|
6
|
+
# Implements the `with_polymorphic_criteria` shared behavior.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
module WithPolymorphicCriteria
|
10
|
+
# If the receiver represents a polymorphic association, applies
|
11
|
+
# the polymorphic search criteria to the given `criteria` object.
|
12
|
+
#
|
13
|
+
# @param [ Mongoid::Criteria ] criteria the criteria to append to
|
14
|
+
# if receiver is polymorphic.
|
15
|
+
# @param [ Mongoid::Document ] base the document to use when resolving
|
16
|
+
# the polymorphic type keys.
|
17
|
+
#
|
18
|
+
# @return [ Mongoid::Criteria] the resulting criteria, which may be
|
19
|
+
# the same as the input.
|
20
|
+
def with_polymorphic_criterion(criteria, base)
|
21
|
+
if polymorphic?
|
22
|
+
# 1. get the resolver for the inverse association
|
23
|
+
resolver = klass.reflect_on_association(as).resolver
|
24
|
+
|
25
|
+
# 2. look up the list of keys from the resolver, given base
|
26
|
+
keys = resolver.keys_for(base)
|
27
|
+
|
28
|
+
# 3. use equality if there is just one key, `in` if there are multiple
|
29
|
+
if keys.many?
|
30
|
+
criteria.where(type => { :$in => keys })
|
31
|
+
else
|
32
|
+
criteria.where(type => keys.first)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
criteria
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -60,7 +60,8 @@ module Mongoid
|
|
60
60
|
re_define_method(meth) do |attrs|
|
61
61
|
_assigning do
|
62
62
|
if association.polymorphic? and association.inverse_type
|
63
|
-
|
63
|
+
klass = association.resolver.model_for(send(association.inverse_type))
|
64
|
+
options = options.merge!(:class_name => klass)
|
64
65
|
end
|
65
66
|
association.nested_builder(attrs, options).build(self)
|
66
67
|
end
|
@@ -86,7 +86,7 @@ module Mongoid
|
|
86
86
|
else
|
87
87
|
PersistenceContext.get(self) ||
|
88
88
|
PersistenceContext.get(self.class) ||
|
89
|
-
PersistenceContext.new(self.class,
|
89
|
+
PersistenceContext.new(self.class, default_storage_options)
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -112,6 +112,19 @@ module Mongoid
|
|
112
112
|
|
113
113
|
private
|
114
114
|
|
115
|
+
def default_storage_options
|
116
|
+
# Nothing is overridden, we use either the default storage_options
|
117
|
+
# or storage_options defined for the document class.
|
118
|
+
return storage_options if Threaded.client_override.nil? && Threaded.database_override.nil?
|
119
|
+
|
120
|
+
storage_options.tap do |opts|
|
121
|
+
# Globally overridden client replaces client defined for the document class.
|
122
|
+
opts[:client] = Threaded.client_override unless Threaded.client_override.nil?
|
123
|
+
# Globally overridden database replaces database defined for the document class.
|
124
|
+
opts[:database] = Threaded.database_override unless Threaded.database_override.nil?
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
115
128
|
def set_persistence_context(options_or_context)
|
116
129
|
PersistenceContext.set(self, options_or_context)
|
117
130
|
end
|
@@ -62,6 +62,7 @@ module Mongoid
|
|
62
62
|
rescue *transactions_not_supported_exceptions
|
63
63
|
raise Mongoid::Errors::TransactionsNotSupported
|
64
64
|
ensure
|
65
|
+
Threaded.clear_modified_documents(session)
|
65
66
|
Threaded.clear_session(client: persistence_context.client)
|
66
67
|
end
|
67
68
|
|
@@ -89,21 +90,22 @@ module Mongoid
|
|
89
90
|
def transaction(options = {}, session_options: {})
|
90
91
|
with_session(session_options) do |session|
|
91
92
|
begin
|
92
|
-
session.
|
93
|
-
|
94
|
-
|
93
|
+
session.with_transaction(options) do
|
94
|
+
yield
|
95
|
+
end
|
96
|
+
run_commit_callbacks(session)
|
95
97
|
rescue *transactions_not_supported_exceptions
|
96
98
|
raise Mongoid::Errors::TransactionsNotSupported
|
97
99
|
rescue Mongoid::Errors::Rollback
|
98
|
-
|
100
|
+
run_abort_callbacks(session)
|
99
101
|
rescue Mongoid::Errors::InvalidSessionNesting
|
100
102
|
# Session should be ended here.
|
101
103
|
raise Mongoid::Errors::InvalidTransactionNesting.new
|
102
104
|
rescue Mongo::Error::InvalidSession, Mongo::Error::InvalidTransactionOperation => e
|
103
|
-
|
104
|
-
raise Mongoid::Errors::TransactionError(e)
|
105
|
+
run_abort_callbacks(session)
|
106
|
+
raise Mongoid::Errors::TransactionError.new(e)
|
105
107
|
rescue StandardError => e
|
106
|
-
|
108
|
+
run_abort_callbacks(session)
|
107
109
|
raise e
|
108
110
|
end
|
109
111
|
end
|
@@ -189,25 +191,21 @@ module Mongoid
|
|
189
191
|
_session&.in_transaction? || false
|
190
192
|
end
|
191
193
|
|
192
|
-
#
|
193
|
-
# after_commit callbacks on modified documents.
|
194
|
+
# Runs after_commit callbacks on modified documents.
|
194
195
|
#
|
195
196
|
# @param [ Mongo::Session ] session Session on which
|
196
197
|
# a transaction is started.
|
197
|
-
def
|
198
|
-
session.commit_transaction
|
198
|
+
def run_commit_callbacks(session)
|
199
199
|
Threaded.clear_modified_documents(session).each do |doc|
|
200
200
|
doc.run_after_callbacks(:commit)
|
201
201
|
end
|
202
202
|
end
|
203
203
|
|
204
|
-
#
|
205
|
-
# after_rollback callbacks on modified documents.
|
204
|
+
# Runs after_rollback callbacks on modified documents.
|
206
205
|
#
|
207
206
|
# @param [ Mongo::Session ] session Session on which
|
208
207
|
# a transaction is started.
|
209
|
-
def
|
210
|
-
session.abort_transaction
|
208
|
+
def run_abort_callbacks(session)
|
211
209
|
Threaded.clear_modified_documents(session).each do |doc|
|
212
210
|
doc.run_after_callbacks(:rollback)
|
213
211
|
end
|
data/lib/mongoid/composable.rb
CHANGED
@@ -5,6 +5,7 @@ require "mongoid/changeable"
|
|
5
5
|
require "mongoid/collection_configurable"
|
6
6
|
require "mongoid/encryptable"
|
7
7
|
require "mongoid/findable"
|
8
|
+
require 'mongoid/identifiable'
|
8
9
|
require "mongoid/indexable"
|
9
10
|
require "mongoid/inspectable"
|
10
11
|
require "mongoid/interceptable"
|
@@ -44,6 +45,7 @@ module Mongoid
|
|
44
45
|
include Attributes
|
45
46
|
include Evolvable
|
46
47
|
include Fields
|
48
|
+
include Identifiable
|
47
49
|
include Indexable
|
48
50
|
include Inspectable
|
49
51
|
include Matchable
|
data/lib/mongoid/document.rb
CHANGED
@@ -17,6 +17,7 @@ require 'mongoid/timestamps'
|
|
17
17
|
require 'mongoid/association'
|
18
18
|
require 'mongoid/composable'
|
19
19
|
require 'mongoid/touchable'
|
20
|
+
require 'mongoid/model_resolver'
|
20
21
|
|
21
22
|
module Mongoid
|
22
23
|
# This is the base module for all domain objects that need to be persisted to
|
@@ -31,6 +32,7 @@ module Mongoid
|
|
31
32
|
|
32
33
|
included do
|
33
34
|
Mongoid.register_model(self)
|
35
|
+
Mongoid::ModelResolver.register(self)
|
34
36
|
end
|
35
37
|
|
36
38
|
# Regex for matching illegal BSON keys.
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Errors
|
5
|
+
# Raised when a polymorphic association is queried, but the type of the
|
6
|
+
# association cannot be resolved. This usually happens when the data in
|
7
|
+
# the database references a type that no longer exists.
|
8
|
+
#
|
9
|
+
# For example, consider the following model:
|
10
|
+
#
|
11
|
+
# class Manager
|
12
|
+
# include Mongoid::Document
|
13
|
+
# belongs_to :unit, polymorphic: true
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Imagine there is a document in the `managers` collection that looks
|
17
|
+
# something like this:
|
18
|
+
#
|
19
|
+
# { _id: ..., unit_id: ..., unit_type: 'Department::Engineering' }
|
20
|
+
#
|
21
|
+
# If, at some point in your refactoring, you rename the `Department::Engineering`
|
22
|
+
# model to something else, Mongoid will no longer be able to resolve the
|
23
|
+
# type of this association, and asking for `manager.unit` will raise this
|
24
|
+
# exception.
|
25
|
+
#
|
26
|
+
# To fix this exception, you can add an alias to the model class so that it
|
27
|
+
# can still be found, even after renaming it:
|
28
|
+
#
|
29
|
+
# module Engineering
|
30
|
+
# class Department
|
31
|
+
# include Mongoid::Document
|
32
|
+
#
|
33
|
+
# identify_as 'Department::Engineering'
|
34
|
+
#
|
35
|
+
# # ...
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Better practice would be to use unique strings instead of class names to
|
40
|
+
# identify these polymorphic types in the database (e.g. 'dept' instead of
|
41
|
+
# 'Department::Engineering').
|
42
|
+
class UnrecognizedModelAlias < MongoidError
|
43
|
+
def initialize(model_alias)
|
44
|
+
super(
|
45
|
+
compose_message(
|
46
|
+
'unrecognized_model_alias',
|
47
|
+
model_alias: model_alias.inspect
|
48
|
+
)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Errors
|
5
|
+
# Raised when a model resolver is referenced, but not registered.
|
6
|
+
#
|
7
|
+
# class Manager
|
8
|
+
# include Mongoid::Document
|
9
|
+
# belongs_to :unit, polymorphic: :org
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# If `:org` has not previously been registered as a model resolver,
|
13
|
+
# Mongoid will raise UnrecognizedResolver when it tries to resolve
|
14
|
+
# a manager's unit.
|
15
|
+
class UnrecognizedResolver < MongoidError
|
16
|
+
def initialize(resolver)
|
17
|
+
super(
|
18
|
+
compose_message(
|
19
|
+
'unrecognized_resolver',
|
20
|
+
resolver: resolver.inspect,
|
21
|
+
resolvers: [ :default, *Mongoid::ModelResolver.resolvers.keys ].inspect
|
22
|
+
)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Errors
|
5
|
+
# Raised when Mongoid tries to query the identifier to use for a given
|
6
|
+
# class in a polymorphic association, but the class has not previously
|
7
|
+
# been registered by resolver that was used for the query.
|
8
|
+
#
|
9
|
+
# Here's an exammple:
|
10
|
+
#
|
11
|
+
# class Department
|
12
|
+
# include Mongoid::Document
|
13
|
+
# has_many :managers, as: :unit
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class Manager
|
17
|
+
# include Mongoid::Document
|
18
|
+
# belongs_to :unit, polymorphic: :org
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# The Manager class is configured to use a custom resolver named `:org`
|
22
|
+
# when resolving the polymorphic `unit` association. However, the `Department`
|
23
|
+
# class is not registered with that resolver. When the program tries to
|
24
|
+
# associate a manager record with a department, it will not be able to find
|
25
|
+
# the required key in the `:org` resolver, and will fail with this exception.
|
26
|
+
#
|
27
|
+
# The solution is to make sure the `Department` class is properly registered
|
28
|
+
# with the `:org` resolver:
|
29
|
+
#
|
30
|
+
# class Department
|
31
|
+
# include Mongoid::Document
|
32
|
+
# identify_as resolver: :org
|
33
|
+
# has_many :managers, as: :unit
|
34
|
+
# end
|
35
|
+
class UnregisteredClass < MongoidError
|
36
|
+
def initialize(klass, resolver)
|
37
|
+
super(
|
38
|
+
compose_message(
|
39
|
+
'unregistered_class',
|
40
|
+
klass: klass,
|
41
|
+
resolver: resolver.inspect
|
42
|
+
)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/mongoid/errors.rb
CHANGED
@@ -69,6 +69,9 @@ require "mongoid/errors/transaction_error"
|
|
69
69
|
require "mongoid/errors/transactions_not_supported"
|
70
70
|
require "mongoid/errors/unknown_attribute"
|
71
71
|
require "mongoid/errors/unknown_model"
|
72
|
+
require 'mongoid/errors/unrecognized_model_alias'
|
73
|
+
require 'mongoid/errors/unrecognized_resolver'
|
74
|
+
require 'mongoid/errors/unregistered_class'
|
72
75
|
require "mongoid/errors/unsaved_document"
|
73
76
|
require "mongoid/errors/unsupported_javascript"
|
74
77
|
require "mongoid/errors/validations"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mongoid/model_resolver'
|
4
|
+
|
5
|
+
module Mongoid
|
6
|
+
# Implements the "identify_as" interface (for specifying type aliases
|
7
|
+
# for document classes).
|
8
|
+
module Identifiable
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
# Specifies aliases that may be used to identify this document
|
13
|
+
# class in polymorphic situations. By default, classes are identified
|
14
|
+
# by their class names, but alternative aliases may be used instead,
|
15
|
+
# if desired.
|
16
|
+
#
|
17
|
+
# @param [ Array<String | Symbol> ] aliases the list of aliases to
|
18
|
+
# assign to this class.
|
19
|
+
# @param [ Mongoid::ModelResolver::Interface | Symbol | :default ] resolver the
|
20
|
+
# resolver instance to use when registering the type. If :default, the default
|
21
|
+
# `ModelResolver` instance will be used. If any other symbol, it must identify a
|
22
|
+
# previously registered ModelResolver instance.
|
23
|
+
def identify_as(*aliases, resolver: :default)
|
24
|
+
Mongoid::ModelResolver.resolver(resolver).register(self, *aliases)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/mongoid/matcher.rb
CHANGED
@@ -39,11 +39,25 @@ module Mongoid
|
|
39
39
|
# from and behaves identically to association traversal for the purposes
|
40
40
|
# of, for example, subsequent array element retrieval.
|
41
41
|
#
|
42
|
-
# @param [ Document | Hash ] document The document to extract from.
|
42
|
+
# @param [ Document | Hash | String ] document The document to extract from.
|
43
43
|
# @param [ String ] key The key path to extract.
|
44
44
|
#
|
45
45
|
# @return [ Object | Array ] Field value or values.
|
46
46
|
module_function def extract_attribute(document, key)
|
47
|
+
# The matcher system will wind up sending atomic values to this as well,
|
48
|
+
# when attepting to match more complex types. If anything other than a
|
49
|
+
# Document or a Hash is given, we'll short-circuit the logic and just
|
50
|
+
# return an empty array.
|
51
|
+
return [] unless document.is_a?(Hash) || document.is_a?(Document)
|
52
|
+
|
53
|
+
# Performance optimization; if the key does not include a '.' character,
|
54
|
+
# it must reference an immediate attribute of the document.
|
55
|
+
unless key.include?('.')
|
56
|
+
hash = document.respond_to?(:attributes) ? document.attributes : document
|
57
|
+
key = find_exact_key(hash, key)
|
58
|
+
return key ? [ hash[key] ] : []
|
59
|
+
end
|
60
|
+
|
47
61
|
if document.respond_to?(:as_attributes, true)
|
48
62
|
# If a document has hash fields, as_attributes would keep those fields
|
49
63
|
# as Hash instances which do not offer indifferent access.
|